var raster = new ol.layer.Tile({ source: new ol.source.OSM() }); var vector = new ol.layer.Vector({ source: new ol.source.Vector({ url: 'https://raw.githubusercontent.com/openlayers/openlayers/master/examples/data/geojson/countries.geojson', format: new ol.format.GeoJSON(), wrapX: false }) }); var selectInt = new ol.interaction.Select({ wrapX: false }); var modify = new ol.interaction.Modify({ features: selectInt.getFeatures() }); var map = new ol.Map({ interactions: ol.interaction.defaults({ //disable double click zoom so used for completing the hole doubleClickZoom: false }).extend([selectInt, modify]), layers: [raster, vector], target: 'map', view: new ol.View({ center: [0, 0], zoom: 2 }) }); //create the style to use for the hole draw interaction var holeStyle = [ new ol.style.Style({ stroke: new ol.style.Stroke({ color: 'rgba(0, 0, 0, 0.8)', lineDash: [10, 10], width: 3 }), fill: new ol.style.Fill({ color: 'rgba(255, 255, 255, 0)' }) }), new ol.style.Style({ image: new ol.style.RegularShape({ fill: new ol.style.Fill({ color: 'rgba(255, 0, 0, 0.5)' }), stroke: new ol.style.Stroke({ color: 'black', width: 1 }), points: 4, radius: 6, angle: Math.PI / 4 }) })]; /** * activates the hole draw interaction * */ document.getElementById('drawhole').onclick = function () { var selFeats = selectInt.getFeatures(); console.log("selFeats.length", selFeats.getLength()); if (selFeats.getLength() !== 1) { alert("need to select only one feature to draw hole"); } else { var geomTypeSelected = selFeats.getArray()[0].getGeometry().getType(); if (geomTypeSelected !== 'Polygon') { alert("Only Polygon geometry selections.Not " + geomTypeSelected); return; } //set the geometry as is before start drawing hole vertices var selFeatGeom = selFeats.getArray()[0].getGeometry().clone(); //create a polygon draw interaction var vertsCouter = 0; //this is the number of vertices drawn on the ol.interaction.Draw(used in the geometryFunction) var holeDraw = new ol.interaction.Draw({ source: new ol.source.Vector(), type: 'Polygon', style: holeStyle, //add the geometry function in order to disable //hole creation outside selected polygon geometryFunction: function (coords, geom) { var retGeom; //define the geometry to return if (typeof (geom) !== 'undefined') { //if it is defined, set its coordinates geom.setCoordinates(coords); } else { retGeom = new ol.geom.Polygon(coords); } if (coords[0].length > vertsCouter) { //this is the case where new vertex has been draw //check if vertex drawn is within the selected polygon var isIn = isPointInPoly(selFeatGeom, coords[0][coords[0].length - 1]); //if outside get rid of it if (isIn === false) { coords[0].pop(); //remove the last coordinate element retGeom = new ol.geom.Polygon(coords); //recunstract the geometry } vertsCouter = coords[0].length; //reset the length of vertex counter } return retGeom; } }); //disable the select and modify interaction during hole drawing selectInt.setActive(false); modify.setActive(false); //add the draw hole interaction map.addInteraction(holeDraw); //add the listener when start drawing holeDraw.on('drawstart', function (e) { var feature = e.feature; //grab the draw hole feature var ringAdded = false; //init boolen var to clarify whether drawn hole has allready been added or not //set the change feature listener so get the hole like visual effect feature.on('change', function (e) { //get draw hole feature geometry var drawCoords = feature.getGeometry().getCoordinates(false)[0]; //if hole has more than two cordinate pairs, add the interior ring to feature if (drawCoords.length > 2) { //if intirior ring has not been added yet, append it and set it as true if (ringAdded === false) { var testCoords = feature.getGeometry().getCoordinates(false)[0]; var testCoordsLength = testCoords.length; //solves the bug when 2nd point outside polygon if (testCoords.length === 3 && testCoords[testCoordsLength - 1][0] === testCoords[testCoordsLength - 2][0] && testCoords[testCoordsLength - 1][1] === testCoords[testCoordsLength - 2][1]) { console.log("do nothing!!!!!") return; } else { selFeats.getArray()[0].getGeometry().appendLinearRing( new ol.geom.LinearRing( pushFirstElement(feature.getGeometry().getCoordinates()[0])) ); ringAdded = true; } } //if interior ring has allready been added so we need to remove it and add back the updated one else { //get the elements length of the geometry var coordElemntLength = selFeats.getArray()[0].getGeometry().getCoordinates().length; var coordsExt = selFeats.getArray()[0].getGeometry().getCoordinates()[0]; //exterior ring var coordsInter = []; //interior rings //if we have more than 2 elements if (coordElemntLength > 2) { //create the interior rings array //note that we use i=1 to leave outside the first element (exterior ring) //and coordElemntLength-1 to leave outside the last element as this is the one we need to update for (var i = 1; i < coordElemntLength - 1; i++) { coordsInter.push(selFeats.getArray()[0].getGeometry().getCoordinates()[i]); } } //construct the coordinates to use for the modify geometry feature var setCoords = []; //if interior rings allready exist if (coordsInter.length > 0) { //push the exterior ring setCoords.push(coordsExt); //push all the interior rings for (var z = 0; z < coordsInter.length; z++) { setCoords.push(coordsInter[z]); } //and finally push the new drawn hole setCoords.push(pushFirstElement(feature.getGeometry().getCoordinates(false)[0])); } else { setCoords = [coordsExt, pushFirstElement(feature.getGeometry().getCoordinates(false)[0])]; } selFeats.getArray()[0].getGeometry().setCoordinates(setCoords); } } }); }); //create a listener when finish drawing and so remove the hole interaction holeDraw.on('drawend', function (evt) { //get rid of the holeDraw interaction map.removeInteraction(holeDraw); //reinitialise modify interaction. If you dont do that, holes may not be modifed //remove the modify interaction map.removeInteraction(modify); //and recreate it using the newly created feature modify = new ol.interaction.Modify({ features: selFeats }); //add the interaction to the map map.addInteraction(modify); //end last..... reactivate the select interaction selectInt.setActive(true); }); } }; /** * check whether the point consists of pointcoords is inside the supplied polygon geometry * @{ol.geometry.Polygon} geom * @{Array()} a two elements array representing the point coordinates * @returns {Boolean} true||false */ function isPointInPoly(geom, pointcoords) { var parser = new jsts.io.OL3Parser(); var retVal = true; try { var geomA = parser.read(geom); var geomB = parser.read(new ol.geom.Point(pointcoords)); retVal = geomB.within(geomA); } catch (e) { console.log("there is some kind of error using jsts--->",e) console.log("return true instead of freezing the whole proccess.",e) retVal = false; } return retVal; }; /** * just a helper function to add first element of array * at the end of the supplied array */ function pushFirstElement(arr){ var retArray = new Array() for (var i=0;i<arr.length;i++){ retArray.push(arr[i]) } retArray.push(arr[0]) return retArray; }
<div id="map" class="map"></div> <button id="drawhole">draw hole</button> <b>select only one feature and the press the "draw hole" button. Draw the hole inside the selected polygon. Just select a simple polyogn to test and not multipolygon</b>