diff --git a/examples/edit-geographic.html b/examples/edit-geographic.html new file mode 100644 index 0000000000..3f66d462ba --- /dev/null +++ b/examples/edit-geographic.html @@ -0,0 +1,15 @@ +--- +layout: example.html +title: Geographic Editing +shortdesc: Editing geometries with geographic coordinates. +docs: > + Calling the useGeographic function in the 'ol/proj' module + makes it so the map view uses geographic coordinates (even if the view projection is + not geographic). +tags: "geographic" +--- +
+ diff --git a/examples/edit-geographic.js b/examples/edit-geographic.js new file mode 100644 index 0000000000..415c780aed --- /dev/null +++ b/examples/edit-geographic.js @@ -0,0 +1,63 @@ +import {Map, View} from '../src/ol/index.js'; +import GeoJSON from '../src/ol/format/GeoJSON.js'; +import {Modify, Select, Draw} from '../src/ol/interaction.js'; +import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; +import {OSM, Vector as VectorSource} from '../src/ol/source.js'; +import {useGeographic} from '../src/ol/proj.js'; + +useGeographic(); + +const source = new VectorSource({ + url: 'data/geojson/countries.geojson', + format: new GeoJSON() +}); + +const map = new Map({ + target: 'map', + layers: [ + new TileLayer({ + source: new OSM() + }), + new VectorLayer({ + source: source + }) + ], + view: new View({ + center: [0, 0], + zoom: 2 + }) +}); + +const select = new Select(); + +const modify = new Modify({ + features: select.getFeatures() +}); + +const draw = new Draw({ + type: 'Polygon', + source: source +}); + +const mode = document.getElementById('mode'); +function onChange() { + switch (mode.value) { + case 'draw': { + map.removeInteraction(modify); + map.removeInteraction(select); + map.addInteraction(draw); + break; + } + case 'modify': { + map.removeInteraction(draw); + map.addInteraction(select); + map.addInteraction(modify); + break; + } + default: { + // pass + } + } +} +mode.addEventListener('change', onChange); +onChange(); diff --git a/src/ol/interaction/Draw.js b/src/ol/interaction/Draw.js index 629a99dda7..49f77f0809 100644 --- a/src/ol/interaction/Draw.js +++ b/src/ol/interaction/Draw.js @@ -417,8 +417,7 @@ class Draw extends PointerInteraction { useSpatialIndex: false, wrapX: options.wrapX ? options.wrapX : false }), - style: options.style ? options.style : - getDefaultStyleFunction(), + style: options.style ? options.style : getDefaultStyleFunction(), updateWhileInteracting: true }); @@ -443,8 +442,7 @@ class Draw extends PointerInteraction { if (options.freehand) { this.freehandCondition_ = always; } else { - this.freehandCondition_ = options.freehandCondition ? - options.freehandCondition : shiftKeyOnly; + this.freehandCondition_ = options.freehandCondition ? options.freehandCondition : shiftKeyOnly; } this.addEventListener(getChangeEventType(InteractionProperty.ACTIVE), this.updateState_); @@ -639,7 +637,7 @@ class Draw extends PointerInteraction { const map = event.map; for (let i = 0, ii = potentiallyFinishCoordinates.length; i < ii; i++) { const finishCoordinate = potentiallyFinishCoordinates[i]; - const finishPixel = map.getPixelFromCoordinateInternal(finishCoordinate); + const finishPixel = map.getPixelFromCoordinate(finishCoordinate); const pixel = event.pixel; const dx = pixel[0] - finishPixel[0]; const dy = pixel[1] - finishPixel[1]; diff --git a/src/ol/interaction/Modify.js b/src/ol/interaction/Modify.js index 5ec421e62e..f949904510 100644 --- a/src/ol/interaction/Modify.js +++ b/src/ol/interaction/Modify.js @@ -11,7 +11,7 @@ import {equals as coordinatesEqual, distance as coordinateDistance, squaredDista import Event from '../events/Event.js'; import EventType from '../events/EventType.js'; import {always, primaryAction, altKeyOnly, singleClick} from '../events/condition.js'; -import {boundingExtent, buffer, createOrUpdateFromCoordinate} from '../extent.js'; +import {boundingExtent, buffer as bufferExtent, createOrUpdateFromCoordinate as createExtent} from '../extent.js'; import GeometryType from '../geom/GeometryType.js'; import Point from '../geom/Point.js'; import PointerInteraction from './Pointer.js'; @@ -20,6 +20,7 @@ import VectorSource from '../source/Vector.js'; import VectorEventType from '../source/VectorEventType.js'; import RBush from '../structs/RBush.js'; import {createEditingStyle} from '../style/Style.js'; +import {fromUserExtent, toUserExtent, fromUserCoordinate, toUserCoordinate} from '../proj.js'; /** @@ -36,6 +37,8 @@ const CIRCLE_CENTER_INDEX = 0; */ const CIRCLE_CIRCUMFERENCE_INDEX = 1; +const tempExtent = [0, 0, 0, 0]; +const tempSegment = []; /** * @enum {string} @@ -652,7 +655,7 @@ class Modify extends PointerInteraction { const featureSegments = [centerSegmentData, circumferenceSegmentData]; centerSegmentData.featureSegments = circumferenceSegmentData.featureSegments = featureSegments; - this.rBush_.insert(createOrUpdateFromCoordinate(coordinates), centerSegmentData); + this.rBush_.insert(createExtent(coordinates), centerSegmentData); this.rBush_.insert(geometry.getExtent(), circumferenceSegmentData); } @@ -801,14 +804,14 @@ class Modify extends PointerInteraction { return false; } this.handlePointerAtPixel_(evt.pixel, evt.map); - const pixelCoordinate = evt.map.getCoordinateFromPixelInternal(evt.pixel); + const pixelCoordinate = evt.coordinate; this.dragSegments_.length = 0; this.modified_ = false; const vertexFeature = this.vertexFeature_; if (vertexFeature) { + const projection = evt.map.getView().getProjection(); const insertVertices = []; - const geometry = vertexFeature.getGeometry(); - const vertex = geometry.getCoordinates(); + const vertex = vertexFeature.getGeometry().getCoordinates(); const vertexExtent = boundingExtent([vertex]); const segmentDataMatches = this.rBush_.getInExtent(vertexExtent); const componentSegments = {}; @@ -824,21 +827,23 @@ class Modify extends PointerInteraction { if (!componentSegments[uid]) { componentSegments[uid] = new Array(2); } - if (segmentDataMatch.geometry.getType() === GeometryType.CIRCLE && - segmentDataMatch.index === CIRCLE_CIRCUMFERENCE_INDEX) { - const closestVertex = closestOnSegmentData(pixelCoordinate, segmentDataMatch); + if (segmentDataMatch.geometry.getType() === GeometryType.CIRCLE && segmentDataMatch.index === CIRCLE_CIRCUMFERENCE_INDEX) { + const closestVertex = closestOnSegmentData(pixelCoordinate, segmentDataMatch, projection); if (coordinatesEqual(closestVertex, vertex) && !componentSegments[uid][0]) { this.dragSegments_.push([segmentDataMatch, 0]); componentSegments[uid][0] = segmentDataMatch; } - } else if (coordinatesEqual(segment[0], vertex) && - !componentSegments[uid][0]) { + continue; + } + + if (coordinatesEqual(segment[0], vertex) && !componentSegments[uid][0]) { this.dragSegments_.push([segmentDataMatch, 0]); componentSegments[uid][0] = segmentDataMatch; - } else if (coordinatesEqual(segment[1], vertex) && - !componentSegments[uid][1]) { + continue; + } + if (coordinatesEqual(segment[1], vertex) && !componentSegments[uid][1]) { // prevent dragging closed linestrings by the connecting node if ((segmentDataMatch.geometry.getType() === GeometryType.LINE_STRING || @@ -851,15 +856,20 @@ class Modify extends PointerInteraction { this.dragSegments_.push([segmentDataMatch, 1]); componentSegments[uid][1] = segmentDataMatch; - } else if (getUid(segment) in this.vertexSegments_ && + continue; + } + + if (getUid(segment) in this.vertexSegments_ && (!componentSegments[uid][0] && !componentSegments[uid][1]) && this.insertVertexCondition_(evt)) { insertVertices.push([segmentDataMatch, vertex]); } } + if (insertVertices.length) { this.willModifyFeatures_(evt); } + for (let j = insertVertices.length - 1; j >= 0; --j) { this.insertVertex_.apply(this, insertVertices[j]); } @@ -881,7 +891,7 @@ class Modify extends PointerInteraction { const circumferenceSegmentData = segmentData.featureSegments[1]; centerSegmentData.segment[0] = centerSegmentData.segment[1] = coordinates; circumferenceSegmentData.segment[0] = circumferenceSegmentData.segment[1] = coordinates; - this.rBush_.update(createOrUpdateFromCoordinate(coordinates), centerSegmentData); + this.rBush_.update(createExtent(coordinates), centerSegmentData); this.rBush_.update(geometry.getExtent(), circumferenceSegmentData); } else { this.rBush_.update(boundingExtent(segmentData.segment), segmentData); @@ -909,14 +919,16 @@ class Modify extends PointerInteraction { * @private */ handlePointerAtPixel_(pixel, map) { - const pixelCoordinate = map.getCoordinateFromPixelInternal(pixel); + const pixelCoordinate = map.getCoordinateFromPixel(pixel); const sortByDistance = function(a, b) { return pointDistanceToSegmentDataSquared(pixelCoordinate, a) - pointDistanceToSegmentDataSquared(pixelCoordinate, b); }; - const box = buffer(createOrUpdateFromCoordinate(pixelCoordinate), - map.getView().getResolution() * this.pixelTolerance_); + const projection = map.getView().getProjection(); + const viewExtent = fromUserExtent(createExtent(pixelCoordinate, tempExtent), projection); + const buffer = map.getView().getResolution() * this.pixelTolerance_; + const box = toUserExtent(bufferExtent(viewExtent, buffer, tempExtent), projection); const rBush = this.rBush_; const nodes = rBush.getInExtent(box); @@ -924,21 +936,19 @@ class Modify extends PointerInteraction { nodes.sort(sortByDistance); const node = nodes[0]; const closestSegment = node.segment; - let vertex = closestOnSegmentData(pixelCoordinate, node); - const vertexPixel = map.getPixelFromCoordinateInternal(vertex); + let vertex = closestOnSegmentData(pixelCoordinate, node, projection); + const vertexPixel = map.getPixelFromCoordinate(vertex); let dist = coordinateDistance(pixel, vertexPixel); if (dist <= this.pixelTolerance_) { /** @type {Object} */ const vertexSegments = {}; - if (node.geometry.getType() === GeometryType.CIRCLE && - node.index === CIRCLE_CIRCUMFERENCE_INDEX) { - + if (node.geometry.getType() === GeometryType.CIRCLE && node.index === CIRCLE_CIRCUMFERENCE_INDEX) { this.snappedToVertex_ = true; this.createOrUpdateVertexFeature_(vertex); } else { - const pixel1 = map.getPixelFromCoordinateInternal(closestSegment[0]); - const pixel2 = map.getPixelFromCoordinateInternal(closestSegment[1]); + const pixel1 = map.getPixelFromCoordinate(closestSegment[0]); + const pixel2 = map.getPixelFromCoordinate(closestSegment[1]); const squaredDist1 = squaredCoordinateDistance(vertexPixel, pixel1); const squaredDist2 = squaredCoordinateDistance(vertexPixel, pixel2); dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); @@ -1251,16 +1261,19 @@ function pointDistanceToSegmentDataSquared(pointCoordinates, segmentData) { * should be found. * @param {SegmentData} segmentData The object describing the line * segment which should contain the closest point. + * @param {import("../proj/Projection.js").default} projection The view projection. * @return {import("../coordinate.js").Coordinate} The point closest to the specified line segment. */ -function closestOnSegmentData(pointCoordinates, segmentData) { +function closestOnSegmentData(pointCoordinates, segmentData, projection) { const geometry = segmentData.geometry; - if (geometry.getType() === GeometryType.CIRCLE && - segmentData.index === CIRCLE_CIRCUMFERENCE_INDEX) { + if (geometry.getType() === GeometryType.CIRCLE && segmentData.index === CIRCLE_CIRCUMFERENCE_INDEX) { return geometry.getClosestPoint(pointCoordinates); } - return closestOnSegment(pointCoordinates, segmentData.segment); + const coordinate = fromUserCoordinate(pointCoordinates, projection); + tempSegment[0] = fromUserCoordinate(segmentData.segment[0], projection); + tempSegment[1] = fromUserCoordinate(segmentData.segment[1], projection); + return toUserCoordinate(closestOnSegment(coordinate, tempSegment), projection); }