mirror of
https://github.com/umap-project/umap.git
synced 2025-04-29 11:52:38 +02:00
wip: (almost) fix tests
This commit is contained in:
parent
7aa07709b3
commit
081323dc8a
9 changed files with 355 additions and 339 deletions
|
@ -23,6 +23,9 @@ class Feature {
|
||||||
this.properties = { _umap_options: {}, ...(geojson.properties || {}) }
|
this.properties = { _umap_options: {}, ...(geojson.properties || {}) }
|
||||||
this.staticOptions = {}
|
this.staticOptions = {}
|
||||||
|
|
||||||
|
if (geojson.coordinates) {
|
||||||
|
geojson = { geometry: geojson }
|
||||||
|
}
|
||||||
if (geojson.geometry) {
|
if (geojson.geometry) {
|
||||||
this.populate(geojson)
|
this.populate(geojson)
|
||||||
}
|
}
|
||||||
|
@ -68,6 +71,10 @@ class Feature {
|
||||||
return this.ui.getCenter()
|
return this.ui.getCenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get bounds() {
|
||||||
|
return this.ui.getBounds()
|
||||||
|
}
|
||||||
|
|
||||||
getClassName() {
|
getClassName() {
|
||||||
return this.staticOptions.className
|
return this.staticOptions.className
|
||||||
}
|
}
|
||||||
|
@ -162,7 +169,7 @@ class Feature {
|
||||||
this.redraw()
|
this.redraw()
|
||||||
}
|
}
|
||||||
|
|
||||||
edit(e) {
|
edit(event) {
|
||||||
if (!this.map.editEnabled || this.isReadOnly()) return
|
if (!this.map.editEnabled || this.isReadOnly()) return
|
||||||
const container = DomUtil.create('div', 'umap-feature-container')
|
const container = DomUtil.create('div', 'umap-feature-container')
|
||||||
DomUtil.createTitle(
|
DomUtil.createTitle(
|
||||||
|
@ -176,7 +183,7 @@ class Feature {
|
||||||
[['datalayer', { handler: 'DataLayerSwitcher' }]],
|
[['datalayer', { handler: 'DataLayerSwitcher' }]],
|
||||||
{
|
{
|
||||||
callback() {
|
callback() {
|
||||||
this.edit(e)
|
this.edit(event)
|
||||||
}, // removeLayer step will close the edit panel, let's reopen it
|
}, // removeLayer step will close the edit panel, let's reopen it
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -207,11 +214,11 @@ class Feature {
|
||||||
builder.helpers['properties.name'].input.focus()
|
builder.helpers['properties.name'].input.focus()
|
||||||
})
|
})
|
||||||
this.map.editedFeature = this
|
this.map.editedFeature = this
|
||||||
if (!this.isOnScreen()) this.zoomTo(e)
|
if (!this.isOnScreen()) this.zoomTo(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
getAdvancedEditActions(container) {
|
getAdvancedEditActions(container) {
|
||||||
DomUtil.createButton('button umap-delete', container, translate('Delete'), (e) => {
|
DomUtil.createButton('button umap-delete', container, translate('Delete'), () => {
|
||||||
this.confirmDelete().then(() => this.map.editPanel.close())
|
this.confirmDelete().then(() => this.map.editPanel.close())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -261,7 +268,7 @@ class Feature {
|
||||||
endEdit() {}
|
endEdit() {}
|
||||||
|
|
||||||
getDisplayName(fallback) {
|
getDisplayName(fallback) {
|
||||||
if (fallback === undefined) fallback = this.datalayer.options.name
|
if (fallback === undefined) fallback = this.datalayer.getName()
|
||||||
const key = this.getOption('labelKey') || 'name'
|
const key = this.getOption('labelKey') || 'name'
|
||||||
// Variables mode.
|
// Variables mode.
|
||||||
if (U.Utils.hasVar(key))
|
if (U.Utils.hasVar(key))
|
||||||
|
@ -308,7 +315,8 @@ class Feature {
|
||||||
|
|
||||||
connectToDataLayer(datalayer) {
|
connectToDataLayer(datalayer) {
|
||||||
this.datalayer = datalayer
|
this.datalayer = datalayer
|
||||||
// this.options.renderer = this.datalayer.renderer
|
// FIXME should be in layer/ui
|
||||||
|
this.ui.options.renderer = this.datalayer.renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnectFromDataLayer(datalayer) {
|
disconnectFromDataLayer(datalayer) {
|
||||||
|
@ -422,16 +430,12 @@ class Feature {
|
||||||
}
|
}
|
||||||
|
|
||||||
toGeoJSON() {
|
toGeoJSON() {
|
||||||
return {
|
return Utils.CopyJSON({
|
||||||
type: 'Feature',
|
type: 'Feature',
|
||||||
geometry: this.geometry,
|
geometry: this.geometry,
|
||||||
properties: this.cloneProperties(),
|
properties: this.cloneProperties(),
|
||||||
id: this.id,
|
id: this.id,
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
getPopupToolbarAnchor() {
|
|
||||||
return [0, 0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getInplaceToolbarActions() {
|
getInplaceToolbarActions() {
|
||||||
|
@ -442,73 +446,6 @@ class Feature {
|
||||||
return this.map
|
return this.map
|
||||||
}
|
}
|
||||||
|
|
||||||
getContextMenuItems(e) {
|
|
||||||
const permalink = this.getPermalink()
|
|
||||||
let items = []
|
|
||||||
if (permalink)
|
|
||||||
items.push({
|
|
||||||
text: translate('Permalink'),
|
|
||||||
callback: () => {
|
|
||||||
window.open(permalink)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (this.map.editEnabled && !this.isReadOnly()) {
|
|
||||||
items = items.concat(this.getContextMenuEditItems(e))
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
getContextMenuEditItems() {
|
|
||||||
let items = ['-']
|
|
||||||
if (this.map.editedFeature !== this) {
|
|
||||||
items.push({
|
|
||||||
text: `${translate('Edit this feature')} (⇧+Click)`,
|
|
||||||
callback: this.edit,
|
|
||||||
context: this,
|
|
||||||
iconCls: 'umap-edit',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
items = items.concat(
|
|
||||||
{
|
|
||||||
text: this.map.help.displayLabel('EDIT_FEATURE_LAYER'),
|
|
||||||
callback: this.datalayer.edit,
|
|
||||||
context: this.datalayer,
|
|
||||||
iconCls: 'umap-edit',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: translate('Delete this feature'),
|
|
||||||
callback: this.confirmDelete,
|
|
||||||
context: this,
|
|
||||||
iconCls: 'umap-delete',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: translate('Clone this feature'),
|
|
||||||
callback: this.clone,
|
|
||||||
context: this,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
resetTooltip() {
|
|
||||||
if (!this.hasGeom()) return
|
|
||||||
const displayName = this.getDisplayName(null)
|
|
||||||
let showLabel = this.getOption('showLabel')
|
|
||||||
const oldLabelHover = this.getOption('labelHover')
|
|
||||||
|
|
||||||
const options = {
|
|
||||||
direction: this.getOption('labelDirection'),
|
|
||||||
interactive: this.getOption('labelInteractive'),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldLabelHover && showLabel) showLabel = null // Retrocompat.
|
|
||||||
options.permanent = showLabel === true
|
|
||||||
this.unbindTooltip()
|
|
||||||
if ((showLabel === true || showLabel === null) && displayName) {
|
|
||||||
this.bindTooltip(Utils.escapeHTML(displayName), options)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isFiltered() {
|
isFiltered() {
|
||||||
const filterKeys = this.datalayer.getFilterKeys()
|
const filterKeys = this.datalayer.getFilterKeys()
|
||||||
const filter = this.map.browser.options.filter
|
const filter = this.map.browser.options.filter
|
||||||
|
@ -519,7 +456,7 @@ class Feature {
|
||||||
|
|
||||||
matchFilter(filter, keys) {
|
matchFilter(filter, keys) {
|
||||||
filter = filter.toLowerCase()
|
filter = filter.toLowerCase()
|
||||||
if (U.Utils.hasVar(keys)) {
|
if (Utils.hasVar(keys)) {
|
||||||
return this.getDisplayName().toLowerCase().indexOf(filter) !== -1
|
return this.getDisplayName().toLowerCase().indexOf(filter) !== -1
|
||||||
}
|
}
|
||||||
keys = keys.split(',')
|
keys = keys.split(',')
|
||||||
|
@ -552,10 +489,6 @@ class Feature {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
getVertexActions() {
|
|
||||||
return [U.DeleteVertexAction]
|
|
||||||
}
|
|
||||||
|
|
||||||
isMulti() {
|
isMulti() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -578,7 +511,7 @@ class Feature {
|
||||||
if (L.lang) properties.lang = L.lang
|
if (L.lang) properties.lang = L.lang
|
||||||
properties.rank = this.getRank() + 1
|
properties.rank = this.getRank() + 1
|
||||||
properties.layer = this.datalayer.getName()
|
properties.layer = this.datalayer.getName()
|
||||||
if (this.map && this.hasGeom()) {
|
if (this.ui._map && this.hasGeom()) {
|
||||||
const center = this.center
|
const center = this.center
|
||||||
properties.lat = center.lat
|
properties.lat = center.lat
|
||||||
properties.lon = center.lng
|
properties.lon = center.lng
|
||||||
|
@ -600,13 +533,6 @@ class Feature {
|
||||||
this.ui._redraw()
|
this.ui._redraw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_showContextMenu(e) {
|
|
||||||
L.DomEvent.stop(e)
|
|
||||||
const pt = this.map.mouseEventToContainerPoint(e.originalEvent)
|
|
||||||
e.relatedTarget = this
|
|
||||||
this.map.contextmenu.showAt(pt, e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Point extends Feature {
|
export class Point extends Feature {
|
||||||
|
@ -630,14 +556,6 @@ export class Point extends Feature {
|
||||||
return new LeafletMarker(this)
|
return new LeafletMarker(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
highlight() {
|
|
||||||
DomUtil.addClass(this.options.icon.elements.main, 'umap-icon-active')
|
|
||||||
}
|
|
||||||
|
|
||||||
resetHighlight() {
|
|
||||||
DomUtil.removeClass(this.options.icon.elements.main, 'umap-icon-active')
|
|
||||||
}
|
|
||||||
|
|
||||||
hasGeom() {
|
hasGeom() {
|
||||||
return Boolean(this.coordinates)
|
return Boolean(this.coordinates)
|
||||||
}
|
}
|
||||||
|
@ -693,10 +611,6 @@ export class Point extends Feature {
|
||||||
bounds = bounds || this.map.getBounds()
|
bounds = bounds || this.map.getBounds()
|
||||||
return bounds.contains(this.coordinates)
|
return bounds.contains(this.coordinates)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPopupToolbarAnchor() {
|
|
||||||
// return this.options.icon.options.popupAnchor
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Path extends Feature {
|
class Path extends Feature {
|
||||||
|
@ -704,10 +618,20 @@ class Path extends Feature {
|
||||||
return !this.isEmpty()
|
return !this.isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get coordinates() {
|
||||||
|
return this._toLatlngs(this.geometry)
|
||||||
|
}
|
||||||
|
|
||||||
|
set coordinates(latlngs) {
|
||||||
|
const { coordinates, type } = this._toGeometry(latlngs)
|
||||||
|
this.geometry.coordinates = coordinates
|
||||||
|
this.geometry.type = type
|
||||||
|
}
|
||||||
|
|
||||||
connectToDataLayer(datalayer) {
|
connectToDataLayer(datalayer) {
|
||||||
super.connectToDataLayer(datalayer)
|
super.connectToDataLayer(datalayer)
|
||||||
// We keep markers on their own layer on top of the paths.
|
// We keep markers on their own layer on top of the paths.
|
||||||
// this.options.pane = this.datalayer.pane
|
this.ui.options.pane = this.datalayer.pane
|
||||||
}
|
}
|
||||||
|
|
||||||
edit(event) {
|
edit(event) {
|
||||||
|
@ -717,17 +641,17 @@ class Path extends Feature {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_toggleEditing(e) {
|
_toggleEditing(event) {
|
||||||
if (this.map.editEnabled) {
|
if (this.map.editEnabled) {
|
||||||
if (this.ui.editEnabled()) {
|
if (this.ui.editEnabled()) {
|
||||||
this.endEdit()
|
this.endEdit()
|
||||||
this.map.editPanel.close()
|
this.map.editPanel.close()
|
||||||
} else {
|
} else {
|
||||||
this.edit(e)
|
this.edit(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// FIXME: disable when disabling global edit
|
// FIXME: disable when disabling global edit
|
||||||
L.DomEvent.stop(e)
|
L.DomEvent.stop(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
getStyleOptions() {
|
getStyleOptions() {
|
||||||
|
@ -772,7 +696,7 @@ class Path extends Feature {
|
||||||
}
|
}
|
||||||
|
|
||||||
getBestZoom() {
|
getBestZoom() {
|
||||||
return this.getOption('zoomTo') || this.map.getBoundsZoom(this.getBounds(), true)
|
return this.getOption('zoomTo') || this.map.getBoundsZoom(this.bounds, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
endEdit() {
|
endEdit() {
|
||||||
|
@ -781,100 +705,36 @@ class Path extends Feature {
|
||||||
}
|
}
|
||||||
|
|
||||||
transferShape(at, to) {
|
transferShape(at, to) {
|
||||||
const shape = this.enableEdit().deleteShapeAt(at)
|
const shape = this.ui.enableEdit().deleteShapeAt(at)
|
||||||
this.disableEdit()
|
// FIXME: make Leaflet.Editable send an event instead
|
||||||
|
this.ui.geometryChanged()
|
||||||
|
this.ui.disableEdit()
|
||||||
if (!shape) return
|
if (!shape) return
|
||||||
to.enableEdit().appendShape(shape)
|
to.ui.enableEdit().appendShape(shape)
|
||||||
if (!this._latlngs.length || !this._latlngs[0].length) this.del()
|
to.ui.geometryChanged()
|
||||||
|
if (this.isEmpty()) this.del()
|
||||||
}
|
}
|
||||||
|
|
||||||
isolateShape(at) {
|
isolateShape(at) {
|
||||||
if (!this.isMulti()) return
|
if (!this.isMulti()) return
|
||||||
const shape = this.enableEdit().deleteShapeAt(at)
|
const shape = this.ui.enableEdit().deleteShapeAt(at)
|
||||||
this.disableEdit()
|
this.ui.disableEdit()
|
||||||
if (!shape) return
|
if (!shape) return
|
||||||
const properties = this.cloneProperties()
|
const properties = this.cloneProperties()
|
||||||
const other = new (this instanceof U.Polyline ? U.Polyline : U.Polygon)(
|
const other = new (this instanceof LineString ? LineString : Polygon)(
|
||||||
this.map,
|
this.datalayer,
|
||||||
shape,
|
|
||||||
{
|
{
|
||||||
geojson: { properties },
|
properties,
|
||||||
|
geometry: this._toGeometry(shape),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
this.datalayer.addLayer(other)
|
this.datalayer.addFeature(other)
|
||||||
other.edit()
|
other.edit()
|
||||||
return other
|
return other
|
||||||
}
|
}
|
||||||
|
|
||||||
getContextMenuItems(e) {
|
getInplaceToolbarActions(event) {
|
||||||
let items = super.getContextMenuItems(e)
|
const items = super.getInplaceToolbarActions(event)
|
||||||
items.push({
|
|
||||||
text: translate('Display measure'),
|
|
||||||
callback() {
|
|
||||||
U.Alert.info(this.getMeasure())
|
|
||||||
},
|
|
||||||
context: this,
|
|
||||||
})
|
|
||||||
if (this.map.editEnabled && !this.isReadOnly() && this.isMulti()) {
|
|
||||||
items = items.concat(this.getContextMenuMultiItems(e))
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
getContextMenuMultiItems(e) {
|
|
||||||
const items = [
|
|
||||||
'-',
|
|
||||||
{
|
|
||||||
text: translate('Remove shape from the multi'),
|
|
||||||
callback() {
|
|
||||||
this.enableEdit().deleteShapeAt(e.latlng)
|
|
||||||
},
|
|
||||||
context: this,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
const shape = this.ui.shapeAt(e.latlng)
|
|
||||||
if (this.ui._latlngs.indexOf(shape) > 0) {
|
|
||||||
items.push({
|
|
||||||
text: translate('Make main shape'),
|
|
||||||
callback() {
|
|
||||||
this.enableEdit().deleteShape(shape)
|
|
||||||
this.editor.prependShape(shape)
|
|
||||||
},
|
|
||||||
context: this,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
getContextMenuEditItems(e) {
|
|
||||||
const items = super.getContextMenuEditItems(e)
|
|
||||||
if (
|
|
||||||
this.map.editedFeature &&
|
|
||||||
this.isSameClass(this.map.editedFeature) &&
|
|
||||||
this.map.editedFeature !== this
|
|
||||||
) {
|
|
||||||
items.push({
|
|
||||||
text: translate('Transfer shape to edited feature'),
|
|
||||||
callback() {
|
|
||||||
this.transferShape(e.latlng, this.map.editedFeature)
|
|
||||||
},
|
|
||||||
context: this,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (this.isMulti()) {
|
|
||||||
items.push({
|
|
||||||
text: translate('Extract shape to separate feature'),
|
|
||||||
callback() {
|
|
||||||
this.isolateShape(e.latlng, this.map.editedFeature)
|
|
||||||
},
|
|
||||||
context: this,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
getInplaceToolbarActions(e) {
|
|
||||||
const items = super.getInplaceToolbarActions(e)
|
|
||||||
if (this.isMulti()) {
|
if (this.isMulti()) {
|
||||||
items.push(U.DeleteShapeAction)
|
items.push(U.DeleteShapeAction)
|
||||||
items.push(U.ExtractShapeFromMultiAction)
|
items.push(U.ExtractShapeFromMultiAction)
|
||||||
|
@ -884,16 +744,16 @@ class Path extends Feature {
|
||||||
|
|
||||||
isOnScreen(bounds) {
|
isOnScreen(bounds) {
|
||||||
bounds = bounds || this.map.getBounds()
|
bounds = bounds || this.map.getBounds()
|
||||||
return bounds.overlaps(this.ui.getBounds())
|
return bounds.overlaps(this.bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomTo({ easing, callback }) {
|
zoomTo({ easing, callback }) {
|
||||||
// Use bounds instead of centroid for paths.
|
// Use bounds instead of centroid for paths.
|
||||||
easing = easing || this.map.getOption('easing')
|
easing = easing || this.map.getOption('easing')
|
||||||
if (easing) {
|
if (easing) {
|
||||||
this.map.flyToBounds(this.getBounds(), this.getBestZoom())
|
this.map.flyToBounds(this.bounds, this.getBestZoom())
|
||||||
} else {
|
} else {
|
||||||
this.map.fitBounds(this.getBounds(), this.getBestZoom() || this.map.getZoom())
|
this.map.fitBounds(this.bounds, this.getBestZoom() || this.map.getZoom())
|
||||||
}
|
}
|
||||||
if (callback) callback.call(this)
|
if (callback) callback.call(this)
|
||||||
}
|
}
|
||||||
|
@ -910,17 +770,22 @@ export class LineString extends Path {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get coordinates() {
|
_toLatlngs(geometry) {
|
||||||
return GeoJSON.coordsToLatLngs(
|
return GeoJSON.coordsToLatLngs(
|
||||||
this.geometry.coordinates,
|
geometry.coordinates,
|
||||||
this.geometry.type === 'LineString' ? 0 : 1
|
geometry.type === 'LineString' ? 0 : 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
set coordinates(latlngs) {
|
_toGeometry(latlngs) {
|
||||||
const multi = !LineUtil.isFlat(latlngs)
|
let multi = !LineUtil.isFlat(latlngs)
|
||||||
this.geometry.coordinates = GeoJSON.latLngsToCoords(latlngs, multi ? 1 : 0, false)
|
let coordinates = GeoJSON.latLngsToCoords(latlngs, multi ? 1 : 0, false)
|
||||||
this.geometry.type = multi ? 'MultiLineString' : 'LineString'
|
if (coordinates.length === 1 && typeof coordinates[0][0] !== 'number') {
|
||||||
|
coordinates = Utils.flattenCoordinates(coordinates)
|
||||||
|
multi = false
|
||||||
|
}
|
||||||
|
const type = multi ? 'MultiLineString' : 'LineString'
|
||||||
|
return { coordinates, type }
|
||||||
}
|
}
|
||||||
|
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
|
@ -940,51 +805,11 @@ export class LineString extends Path {
|
||||||
return L.GeoUtil.readableDistance(length, this.map.measureTools.getMeasureUnit())
|
return L.GeoUtil.readableDistance(length, this.map.measureTools.getMeasureUnit())
|
||||||
}
|
}
|
||||||
|
|
||||||
getContextMenuEditItems(e) {
|
|
||||||
const items = super.getContextMenuEditItems(e)
|
|
||||||
const vertexClicked = e.vertex
|
|
||||||
let index
|
|
||||||
if (!this.isMulti()) {
|
|
||||||
items.push({
|
|
||||||
text: translate('Transform to polygon'),
|
|
||||||
callback: this.toPolygon,
|
|
||||||
context: this,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (vertexClicked) {
|
|
||||||
index = e.vertex.getIndex()
|
|
||||||
if (index !== 0 && index !== e.vertex.getLastIndex()) {
|
|
||||||
items.push({
|
|
||||||
text: translate('Split line'),
|
|
||||||
callback: e.vertex.split,
|
|
||||||
context: e.vertex,
|
|
||||||
})
|
|
||||||
} else if (index === 0 || index === e.vertex.getLastIndex()) {
|
|
||||||
items.push({
|
|
||||||
text: this.map.help.displayLabel('CONTINUE_LINE'),
|
|
||||||
callback: e.vertex.continue,
|
|
||||||
context: e.vertex.continue,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
getContextMenuMultiItems(e) {
|
|
||||||
const items = super.getContextMenuMultiItems(e)
|
|
||||||
items.push({
|
|
||||||
text: translate('Merge lines'),
|
|
||||||
callback: this.mergeShapes,
|
|
||||||
context: this,
|
|
||||||
})
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
toPolygon() {
|
toPolygon() {
|
||||||
const geojson = this.toGeoJSON()
|
const geojson = this.toGeoJSON()
|
||||||
geojson.geometry.type = 'Polygon'
|
geojson.geometry.type = 'Polygon'
|
||||||
geojson.geometry.coordinates = [
|
geojson.geometry.coordinates = [
|
||||||
U.Utils.flattenCoordinates(geojson.geometry.coordinates),
|
Utils.flattenCoordinates(geojson.geometry.coordinates),
|
||||||
]
|
]
|
||||||
|
|
||||||
delete geojson.id // delete the copied id, a new one will be generated.
|
delete geojson.id // delete the copied id, a new one will be generated.
|
||||||
|
@ -1053,15 +878,6 @@ export class LineString extends Path {
|
||||||
isMulti() {
|
isMulti() {
|
||||||
return !LineUtil.isFlat(this.coordinates) && this.coordinates.length > 1
|
return !LineUtil.isFlat(this.coordinates) && this.coordinates.length > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
getVertexActions(e) {
|
|
||||||
const actions = super.getVertexActions(e)
|
|
||||||
const index = e.vertex.getIndex()
|
|
||||||
if (index === 0 || index === e.vertex.getLastIndex())
|
|
||||||
actions.push(U.ContinueLineAction)
|
|
||||||
else actions.push(U.SplitLineAction)
|
|
||||||
return actions
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Polygon extends Path {
|
export class Polygon extends Path {
|
||||||
|
@ -1073,22 +889,22 @@ export class Polygon extends Path {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get coordinates() {
|
_toLatlngs(geometry) {
|
||||||
return GeoJSON.coordsToLatLngs(
|
return GeoJSON.coordsToLatLngs(
|
||||||
this.geometry.coordinates,
|
geometry.coordinates,
|
||||||
this.geometry.type === 'Polygon' ? 1 : 2
|
geometry.type === 'Polygon' ? 1 : 2
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
set coordinates(latlngs) {
|
_toGeometry(latlngs) {
|
||||||
const holes = !LineUtil.isFlat(latlngs)
|
const holes = !LineUtil.isFlat(latlngs)
|
||||||
const multi = holes && !LineUtil.isFlat(latlngs[0])
|
const multi = holes && !LineUtil.isFlat(latlngs[0])
|
||||||
let coords = GeoJSON.latLngsToCoords(latlngs, multi ? 2 : holes ? 1 : 0, true)
|
let coordinates = GeoJSON.latLngsToCoords(latlngs, multi ? 2 : holes ? 1 : 0, true)
|
||||||
if (!holes) {
|
if (!holes) {
|
||||||
coords = [coords]
|
coordinates = [coordinates]
|
||||||
}
|
}
|
||||||
this.geometry.coordinates = coords
|
const type = multi ? 'MultiPolygon' : 'Polygon'
|
||||||
this.geometry.type = multi ? 'MultiPolygon' : 'Polygon'
|
return { coordinates, type }
|
||||||
}
|
}
|
||||||
|
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
|
@ -1133,35 +949,12 @@ export class Polygon extends Path {
|
||||||
return L.GeoUtil.readableArea(area, this.map.measureTools.getMeasureUnit())
|
return L.GeoUtil.readableArea(area, this.map.measureTools.getMeasureUnit())
|
||||||
}
|
}
|
||||||
|
|
||||||
getContextMenuEditItems(e) {
|
toLineString() {
|
||||||
const items = super.getContextMenuEditItems(e)
|
|
||||||
const shape = this.ui.shapeAt(e.latlng)
|
|
||||||
// No multi and no holes.
|
|
||||||
if (shape && !this.isMulti() && (LineUtil.isFlat(shape) || shape.length === 1)) {
|
|
||||||
items.push({
|
|
||||||
text: translate('Transform to lines'),
|
|
||||||
callback: this.toPolyline,
|
|
||||||
context: this,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
items.push({
|
|
||||||
text: translate('Start a hole here'),
|
|
||||||
callback: this.startHole,
|
|
||||||
context: this,
|
|
||||||
})
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
startHole(event) {
|
|
||||||
this.ui.enableEdit().newHole(event.latlng)
|
|
||||||
}
|
|
||||||
|
|
||||||
toPolyline() {
|
|
||||||
const geojson = this.toGeoJSON()
|
const geojson = this.toGeoJSON()
|
||||||
delete geojson.id
|
delete geojson.id
|
||||||
delete geojson.properties.id
|
delete geojson.properties.id
|
||||||
geojson.geometry.type = 'LineString'
|
geojson.geometry.type = 'LineString'
|
||||||
geojson.geometry.coordinates = U.Utils.flattenCoordinates(
|
geojson.geometry.coordinates = Utils.flattenCoordinates(
|
||||||
geojson.geometry.coordinates
|
geojson.geometry.coordinates
|
||||||
)
|
)
|
||||||
const polyline = this.datalayer.geojsonToFeatures(geojson)
|
const polyline = this.datalayer.geojsonToFeatures(geojson)
|
||||||
|
@ -1171,17 +964,18 @@ export class Polygon extends Path {
|
||||||
|
|
||||||
getAdvancedEditActions(container) {
|
getAdvancedEditActions(container) {
|
||||||
super.getAdvancedEditActions(container)
|
super.getAdvancedEditActions(container)
|
||||||
const toPolyline = DomUtil.createButton(
|
const toLineString = DomUtil.createButton(
|
||||||
'button umap-to-polyline',
|
'button umap-to-polyline',
|
||||||
container,
|
container,
|
||||||
translate('Transform to lines'),
|
translate('Transform to lines'),
|
||||||
this.toPolyline,
|
this.toLineString,
|
||||||
this
|
this
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
isMulti() {
|
isMulti() {
|
||||||
// Change me when Leaflet#3279 is merged.
|
// Change me when Leaflet#3279 is merged.
|
||||||
|
// FIXME use TurfJS
|
||||||
return (
|
return (
|
||||||
!LineUtil.isFlat(this.coordinates) &&
|
!LineUtil.isFlat(this.coordinates) &&
|
||||||
!LineUtil.isFlat(this.coordinates[0]) &&
|
!LineUtil.isFlat(this.coordinates[0]) &&
|
||||||
|
@ -1189,8 +983,8 @@ export class Polygon extends Path {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getInplaceToolbarActions(e) {
|
getInplaceToolbarActions(event) {
|
||||||
const items = super.getInplaceToolbarActions(e)
|
const items = super.getInplaceToolbarActions(event)
|
||||||
items.push(U.CreateHoleAction)
|
items.push(U.CreateHoleAction)
|
||||||
return items
|
return items
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ export class DataLayer {
|
||||||
this.parentPane = this.map.getPane('overlayPane')
|
this.parentPane = this.map.getPane('overlayPane')
|
||||||
this.pane = this.map.createPane(`datalayer${stamp(this)}`, this.parentPane)
|
this.pane = this.map.createPane(`datalayer${stamp(this)}`, this.parentPane)
|
||||||
this.pane.dataset.id = stamp(this)
|
this.pane.dataset.id = stamp(this)
|
||||||
|
// FIXME: should be on layer
|
||||||
this.renderer = L.svg({ pane: this.pane })
|
this.renderer = L.svg({ pane: this.pane })
|
||||||
this.defaultOptions = {
|
this.defaultOptions = {
|
||||||
displayOnLoad: true,
|
displayOnLoad: true,
|
||||||
|
@ -481,11 +482,10 @@ export class DataLayer {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if (feature) {
|
if (feature) {
|
||||||
feature.setLatLng(latlng)
|
feature.coordinates = latlng
|
||||||
return feature
|
return feature
|
||||||
}
|
}
|
||||||
return new Point(this, geojson)
|
return new Point(this, geojson)
|
||||||
// return this._pointToLayer(geojson, latlng, id)
|
|
||||||
|
|
||||||
case 'MultiLineString':
|
case 'MultiLineString':
|
||||||
case 'LineString':
|
case 'LineString':
|
||||||
|
@ -495,21 +495,19 @@ export class DataLayer {
|
||||||
)
|
)
|
||||||
if (!latlngs.length) break
|
if (!latlngs.length) break
|
||||||
if (feature) {
|
if (feature) {
|
||||||
feature.setLatLngs(latlngs)
|
feature.coordinates = latlngs
|
||||||
return feature
|
return feature
|
||||||
}
|
}
|
||||||
return new LineString(this, geojson)
|
return new LineString(this, geojson)
|
||||||
// return this._lineToLayer(geojson, latlngs, id)
|
|
||||||
|
|
||||||
case 'MultiPolygon':
|
case 'MultiPolygon':
|
||||||
case 'Polygon':
|
case 'Polygon':
|
||||||
latlngs = GeoJSON.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2)
|
latlngs = GeoJSON.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2)
|
||||||
if (feature) {
|
if (feature) {
|
||||||
feature.setLatLngs(latlngs)
|
feature.coordinates = latlngs
|
||||||
return feature
|
return feature
|
||||||
}
|
}
|
||||||
return new Polygon(this, geojson)
|
return new Polygon(this, geojson)
|
||||||
// return this._polygonToLayer(geojson, latlngs, id)
|
|
||||||
case 'GeometryCollection':
|
case 'GeometryCollection':
|
||||||
return this.geojsonToFeatures(geometry.geometries)
|
return this.geojsonToFeatures(geometry.geometries)
|
||||||
|
|
||||||
|
@ -900,9 +898,9 @@ export class DataLayer {
|
||||||
getOption(option, feature) {
|
getOption(option, feature) {
|
||||||
if (this.layer?.getOption) {
|
if (this.layer?.getOption) {
|
||||||
const value = this.layer.getOption(option, feature)
|
const value = this.layer.getOption(option, feature)
|
||||||
if (typeof value !== 'undefined') return value
|
if (value !== undefined) return value
|
||||||
}
|
}
|
||||||
if (typeof this.getOwnOption(option) !== 'undefined') {
|
if (this.getOwnOption(option) !== undefined) {
|
||||||
return this.getOwnOption(option)
|
return this.getOwnOption(option)
|
||||||
}
|
}
|
||||||
if (this.layer?.defaults?.[option]) {
|
if (this.layer?.defaults?.[option]) {
|
||||||
|
@ -1179,7 +1177,7 @@ export class DataLayer {
|
||||||
// By default, it will we use the "name" property, which is also the one used as label in the features list.
|
// By default, it will we use the "name" property, which is also the one used as label in the features list.
|
||||||
// When map owner has configured another label or sort key, we try to be smart and search in the same keys.
|
// When map owner has configured another label or sort key, we try to be smart and search in the same keys.
|
||||||
if (this.map.options.filterKey) return this.map.options.filterKey
|
if (this.map.options.filterKey) return this.map.options.filterKey
|
||||||
if (this.options.labelKey) return this.options.labelKey
|
if (this.getOption('labelKey')) return this.getOption('labelKey')
|
||||||
if (this.map.options.sortKey) return this.map.options.sortKey
|
if (this.map.options.sortKey) return this.map.options.sortKey
|
||||||
return 'name'
|
return 'name'
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const EXPORT_FORMATS = {
|
||||||
const table = []
|
const table = []
|
||||||
map.eachFeature((feature) => {
|
map.eachFeature((feature) => {
|
||||||
const row = feature.toGeoJSON().properties
|
const row = feature.toGeoJSON().properties
|
||||||
const center = feature.getCenter()
|
const center = feature.center
|
||||||
delete row._umap_options
|
delete row._umap_options
|
||||||
row.Latitude = center.lat
|
row.Latitude = center.lat
|
||||||
row.Longitude = center.lng
|
row.Longitude = center.lng
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import { translate } from '../../i18n.js'
|
import { translate } from '../../i18n.js'
|
||||||
import { LayerMixin } from './base.js'
|
import { LayerMixin } from './base.js'
|
||||||
import * as Utils from '../../utils.js'
|
import * as Utils from '../../utils.js'
|
||||||
|
import { Evented } from '../../../../vendors/leaflet/leaflet-src.esm.js'
|
||||||
|
|
||||||
const MarkerCluster = L.MarkerCluster.extend({
|
const MarkerCluster = L.MarkerCluster.extend({
|
||||||
// Custom class so we can call computeTextColor
|
// Custom class so we can call computeTextColor
|
||||||
|
@ -49,7 +50,6 @@ export const Cluster = L.MarkerClusterGroup.extend({
|
||||||
return L.MarkerClusterGroup.prototype.onAdd.call(this, map)
|
return L.MarkerClusterGroup.prototype.onAdd.call(this, map)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
onRemove: function (map) {
|
onRemove: function (map) {
|
||||||
// In some situation, the onRemove is called before the layer is really
|
// In some situation, the onRemove is called before the layer is really
|
||||||
// added to the map: basically when combining a defaultView=data + max/minZoom
|
// added to the map: basically when combining a defaultView=data + max/minZoom
|
||||||
|
@ -100,4 +100,9 @@ export const Cluster = L.MarkerClusterGroup.extend({
|
||||||
this.options.polygonOptions.color = this.datalayer.getColor()
|
this.options.polygonOptions.color = this.datalayer.getColor()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// listens: function (type, propagate) {
|
||||||
|
// L.MarkerClusterGroup.prototype.listens.call(this, type, propagate)
|
||||||
|
// return Evented.prototype.listens.call(this, type, propagate)
|
||||||
|
// },
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
|
// Goes here all code related to Leaflet, DOM and user interactions.
|
||||||
import {
|
import {
|
||||||
Marker,
|
Marker,
|
||||||
Polyline,
|
Polyline,
|
||||||
Polygon,
|
Polygon,
|
||||||
DomUtil,
|
DomUtil,
|
||||||
|
LineUtil,
|
||||||
} from '../../../vendors/leaflet/leaflet-src.esm.js'
|
} from '../../../vendors/leaflet/leaflet-src.esm.js'
|
||||||
import { translate } from '../i18n.js'
|
import { translate } from '../i18n.js'
|
||||||
|
import { uMapAlert as Alert } from '../../components/alerts/alert.js'
|
||||||
|
import * as Utils from '../utils.js'
|
||||||
|
|
||||||
const FeatureMixin = {
|
const FeatureMixin = {
|
||||||
initialize: function (feature) {
|
initialize: function (feature) {
|
||||||
|
@ -27,15 +31,88 @@ const FeatureMixin = {
|
||||||
},
|
},
|
||||||
|
|
||||||
addInteractions: function () {
|
addInteractions: function () {
|
||||||
this.on('contextmenu editable:vertex:contextmenu', this.feature._showContextMenu, this.feature)
|
this.on('contextmenu editable:vertex:contextmenu', this._showContextMenu)
|
||||||
},
|
},
|
||||||
|
|
||||||
onVertexRawClick: function (e) {
|
resetTooltip: function () {
|
||||||
new L.Toolbar.Popup(e.latlng, {
|
if (!this.feature.hasGeom()) return
|
||||||
className: 'leaflet-inplace-toolbar',
|
const displayName = this.feature.getDisplayName(null)
|
||||||
actions: this.getVertexActions(e),
|
let showLabel = this.feature.getOption('showLabel')
|
||||||
}).addTo(this.map, this, e.latlng, e.vertex)
|
const oldLabelHover = this.feature.getOption('labelHover')
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
direction: this.feature.getOption('labelDirection'),
|
||||||
|
interactive: this.feature.getOption('labelInteractive'),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldLabelHover && showLabel) showLabel = null // Retrocompat.
|
||||||
|
options.permanent = showLabel === true
|
||||||
|
this.unbindTooltip()
|
||||||
|
if ((showLabel === true || showLabel === null) && displayName) {
|
||||||
|
this.bindTooltip(Utils.escapeHTML(displayName), options)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_showContextMenu: function (event) {
|
||||||
|
L.DomEvent.stop(event)
|
||||||
|
const pt = this._map.mouseEventToContainerPoint(event.originalEvent)
|
||||||
|
event.relatedTarget = this
|
||||||
|
this._map.contextmenu.showAt(pt, event)
|
||||||
|
},
|
||||||
|
|
||||||
|
getContextMenuItems: function (event) {
|
||||||
|
const permalink = this.feature.getPermalink()
|
||||||
|
let items = []
|
||||||
|
if (permalink)
|
||||||
|
items.push({
|
||||||
|
text: translate('Permalink'),
|
||||||
|
callback: () => {
|
||||||
|
window.open(permalink)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (this._map.editEnabled && !this.feature.isReadOnly()) {
|
||||||
|
items = items.concat(this.getContextMenuEditItems(event))
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
},
|
||||||
|
|
||||||
|
getContextMenuEditItems: function () {
|
||||||
|
let items = ['-']
|
||||||
|
if (this._map.editedFeature !== this) {
|
||||||
|
items.push({
|
||||||
|
text: `${translate('Edit this feature')} (⇧+Click)`,
|
||||||
|
callback: this.feature.edit,
|
||||||
|
context: this.feature,
|
||||||
|
iconCls: 'umap-edit',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
items = items.concat(
|
||||||
|
{
|
||||||
|
text: this._map.help.displayLabel('EDIT_FEATURE_LAYER'),
|
||||||
|
callback: this.feature.datalayer.edit,
|
||||||
|
context: this.feature.datalayer,
|
||||||
|
iconCls: 'umap-edit',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: translate('Delete this feature'),
|
||||||
|
callback: this.feature.confirmDelete,
|
||||||
|
context: this.feature,
|
||||||
|
iconCls: 'umap-delete',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: translate('Clone this feature'),
|
||||||
|
callback: this.feature.clone,
|
||||||
|
context: this.feature,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
},
|
||||||
|
|
||||||
|
onCommit: function () {
|
||||||
|
this.geometryChanged()
|
||||||
|
this.feature.onCommit()
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LeafletMarker = Marker.extend({
|
export const LeafletMarker = Marker.extend({
|
||||||
|
@ -47,22 +124,17 @@ export const LeafletMarker = Marker.extend({
|
||||||
this.setIcon(this.getIcon())
|
this.setIcon(this.getIcon())
|
||||||
},
|
},
|
||||||
|
|
||||||
onCommit: function () {
|
geometryChanged: function() {
|
||||||
this.feature.coordinates = this._latlng
|
this.feature.coordinates = this._latlng
|
||||||
this.feature.onCommit()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addInteractions() {
|
addInteractions() {
|
||||||
FeatureMixin.addInteractions.call(this)
|
FeatureMixin.addInteractions.call(this)
|
||||||
this.on(
|
this.on('dragend', (event) => {
|
||||||
'dragend',
|
this.isDirty = true
|
||||||
function (e) {
|
this.feature.edit(event)
|
||||||
this.isDirty = true
|
this.feature.sync.update('geometry', this.feature.getGeometry())
|
||||||
this.feature.edit(e)
|
})
|
||||||
this.feature.sync.update('geometry', this.getGeometry())
|
|
||||||
},
|
|
||||||
this
|
|
||||||
)
|
|
||||||
this.on('editable:drawing:commit', this.onCommit)
|
this.on('editable:drawing:commit', this.onCommit)
|
||||||
if (!this.feature.isReadOnly()) this.on('mouseover', this._enableDragging)
|
if (!this.feature.isReadOnly()) this.on('mouseover', this._enableDragging)
|
||||||
this.on('mouseout', this._onMouseOut)
|
this.on('mouseout', this._onMouseOut)
|
||||||
|
@ -104,7 +176,7 @@ export const LeafletMarker = Marker.extend({
|
||||||
Marker.prototype._initIcon.call(this)
|
Marker.prototype._initIcon.call(this)
|
||||||
// Allow to run code when icon is actually part of the DOM
|
// Allow to run code when icon is actually part of the DOM
|
||||||
this.options.icon.onAdd()
|
this.options.icon.onAdd()
|
||||||
// this.resetTooltip()
|
this.resetTooltip()
|
||||||
},
|
},
|
||||||
|
|
||||||
getIconClass: function () {
|
getIconClass: function () {
|
||||||
|
@ -118,7 +190,7 @@ export const LeafletMarker = Marker.extend({
|
||||||
|
|
||||||
_getTooltipAnchor: function () {
|
_getTooltipAnchor: function () {
|
||||||
const anchor = this.options.icon.options.tooltipAnchor.clone()
|
const anchor = this.options.icon.options.tooltipAnchor.clone()
|
||||||
const direction = this.getOption('labelDirection')
|
const direction = this.feature.getOption('labelDirection')
|
||||||
if (direction === 'left') {
|
if (direction === 'left') {
|
||||||
anchor.x *= -1
|
anchor.x *= -1
|
||||||
} else if (direction === 'bottom') {
|
} else if (direction === 'bottom') {
|
||||||
|
@ -138,6 +210,14 @@ export const LeafletMarker = Marker.extend({
|
||||||
getCenter: function () {
|
getCenter: function () {
|
||||||
return this._latlng
|
return this._latlng
|
||||||
},
|
},
|
||||||
|
|
||||||
|
highlight: function () {
|
||||||
|
DomUtil.addClass(this.options.icon.elements.main, 'umap-icon-active')
|
||||||
|
},
|
||||||
|
|
||||||
|
resetHighlight: function () {
|
||||||
|
DomUtil.removeClass(this.options.icon.elements.main, 'umap-icon-active')
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const PathMixin = {
|
const PathMixin = {
|
||||||
|
@ -149,9 +229,8 @@ const PathMixin = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onCommit: function () {
|
geometryChanged: function () {
|
||||||
this.feature.coordinates = this._latlngs
|
this.feature.coordinates = this._latlngs
|
||||||
this.feature.onCommit()
|
|
||||||
},
|
},
|
||||||
|
|
||||||
addInteractions: function () {
|
addInteractions: function () {
|
||||||
|
@ -172,7 +251,7 @@ const PathMixin = {
|
||||||
},
|
},
|
||||||
|
|
||||||
_onDrag: function () {
|
_onDrag: function () {
|
||||||
this.feature.coordinates = this._latlngs
|
this.geometryChanged()
|
||||||
if (this._tooltip) this._tooltip.setLatLng(this.getCenter())
|
if (this._tooltip) this._tooltip.setLatLng(this.getCenter())
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -181,7 +260,7 @@ const PathMixin = {
|
||||||
this.setStyle()
|
this.setStyle()
|
||||||
FeatureMixin.onAdd.call(this, map)
|
FeatureMixin.onAdd.call(this, map)
|
||||||
if (this.editing?.enabled()) this.editing.addHooks()
|
if (this.editing?.enabled()) this.editing.addHooks()
|
||||||
// this.resetTooltip()
|
this.resetTooltip()
|
||||||
this._path.dataset.feature = this.feature.id
|
this._path.dataset.feature = this.feature.id
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -200,16 +279,162 @@ const PathMixin = {
|
||||||
|
|
||||||
_redraw: function () {
|
_redraw: function () {
|
||||||
this.setStyle()
|
this.setStyle()
|
||||||
// this.resetTooltip()
|
this.resetTooltip()
|
||||||
|
},
|
||||||
|
|
||||||
|
getVertexActions: () => [U.DeleteVertexAction],
|
||||||
|
|
||||||
|
onVertexRawClick: function (event) {
|
||||||
|
new L.Toolbar.Popup(event.latlng, {
|
||||||
|
className: 'leaflet-inplace-toolbar',
|
||||||
|
actions: this.getVertexActions(event),
|
||||||
|
}).addTo(this._map, this, event.latlng, event.vertex)
|
||||||
|
},
|
||||||
|
|
||||||
|
getContextMenuItems: function (event) {
|
||||||
|
let items = FeatureMixin.getContextMenuItems.call(this, event)
|
||||||
|
items.push({
|
||||||
|
text: translate('Display measure'),
|
||||||
|
callback: () => Alert.info(this.feature.getMeasure()),
|
||||||
|
})
|
||||||
|
if (this._map.editEnabled && !this.feature.isReadOnly() && this.feature.isMulti()) {
|
||||||
|
items = items.concat(this.getContextMenuMultiItems(event))
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
},
|
||||||
|
|
||||||
|
getContextMenuMultiItems: function (event) {
|
||||||
|
const items = [
|
||||||
|
'-',
|
||||||
|
{
|
||||||
|
text: translate('Remove shape from the multi'),
|
||||||
|
callback: () => {
|
||||||
|
this.enableEdit().deleteShapeAt(event.latlng)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const shape = this.shapeAt(event.latlng)
|
||||||
|
if (this._latlngs.indexOf(shape) > 0) {
|
||||||
|
items.push({
|
||||||
|
text: translate('Make main shape'),
|
||||||
|
callback: () => {
|
||||||
|
this.enableEdit().deleteShape(shape)
|
||||||
|
this.editor.prependShape(shape)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
},
|
||||||
|
|
||||||
|
getContextMenuEditItems: function (event) {
|
||||||
|
const items = FeatureMixin.getContextMenuEditItems.call(this, event)
|
||||||
|
if (
|
||||||
|
this._map?.editedFeature !== this &&
|
||||||
|
this.feature.isSameClass(this._map.editedFeature)
|
||||||
|
) {
|
||||||
|
items.push({
|
||||||
|
text: translate('Transfer shape to edited feature'),
|
||||||
|
callback: () => {
|
||||||
|
this.feature.transferShape(event.latlng, this._map.editedFeature)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (this.feature.isMulti()) {
|
||||||
|
items.push({
|
||||||
|
text: translate('Extract shape to separate feature'),
|
||||||
|
callback: () => {
|
||||||
|
this.feature.isolateShape(event.latlng, this._map.editedFeature)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return items
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LeafletPolyline = Polyline.extend({
|
export const LeafletPolyline = Polyline.extend({
|
||||||
parentClass: Polyline,
|
parentClass: Polyline,
|
||||||
includes: [FeatureMixin, PathMixin],
|
includes: [FeatureMixin, PathMixin],
|
||||||
|
|
||||||
|
getVertexActions: function (event) {
|
||||||
|
const actions = PathMixin.getVertexActions.call(this, event)
|
||||||
|
const index = event.vertex.getIndex()
|
||||||
|
if (index === 0 || index === event.vertex.getLastIndex()) {
|
||||||
|
actions.push(U.ContinueLineAction)
|
||||||
|
} else {
|
||||||
|
actions.push(U.SplitLineAction)
|
||||||
|
}
|
||||||
|
return actions
|
||||||
|
},
|
||||||
|
|
||||||
|
getContextMenuEditItems: function (event) {
|
||||||
|
const items = PathMixin.getContextMenuEditItems.call(this, event)
|
||||||
|
const vertexClicked = event.vertex
|
||||||
|
let index
|
||||||
|
if (!this.feature.isMulti()) {
|
||||||
|
items.push({
|
||||||
|
text: translate('Transform to polygon'),
|
||||||
|
callback: this.feature.toPolygon,
|
||||||
|
context: this.feature,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (vertexClicked) {
|
||||||
|
index = event.vertex.getIndex()
|
||||||
|
if (index !== 0 && index !== event.vertex.getLastIndex()) {
|
||||||
|
items.push({
|
||||||
|
text: translate('Split line'),
|
||||||
|
callback: event.vertex.split,
|
||||||
|
context: event.vertex,
|
||||||
|
})
|
||||||
|
} else if (index === 0 || index === event.vertex.getLastIndex()) {
|
||||||
|
items.push({
|
||||||
|
text: this._map.help.displayLabel('CONTINUE_LINE'),
|
||||||
|
callback: event.vertex.continue,
|
||||||
|
context: event.vertex.continue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
},
|
||||||
|
|
||||||
|
getContextMenuMultiItems: function (event) {
|
||||||
|
const items = PathMixin.getContextMenuMultiItems.call(this, event)
|
||||||
|
items.push({
|
||||||
|
text: translate('Merge lines'),
|
||||||
|
callback: this.feature.mergeShapes,
|
||||||
|
context: this.feature,
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export const LeafletPolygon = Polygon.extend({
|
export const LeafletPolygon = Polygon.extend({
|
||||||
parentClass: Polygon,
|
parentClass: Polygon,
|
||||||
includes: [FeatureMixin, PathMixin],
|
includes: [FeatureMixin, PathMixin],
|
||||||
|
|
||||||
|
getContextMenuEditItems: function (event) {
|
||||||
|
const items = PathMixin.getContextMenuEditItems.call(this, event)
|
||||||
|
const shape = this.shapeAt(event.latlng)
|
||||||
|
// No multi and no holes.
|
||||||
|
if (
|
||||||
|
shape &&
|
||||||
|
!this.feature.isMulti() &&
|
||||||
|
(LineUtil.isFlat(shape) || shape.length === 1)
|
||||||
|
) {
|
||||||
|
items.push({
|
||||||
|
text: translate('Transform to lines'),
|
||||||
|
callback: this.feature.toLineString,
|
||||||
|
context: this.feature,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
items.push({
|
||||||
|
text: translate('Start a hole here'),
|
||||||
|
callback: this.startHole,
|
||||||
|
context: this,
|
||||||
|
})
|
||||||
|
return items
|
||||||
|
},
|
||||||
|
|
||||||
|
startHole: function (event) {
|
||||||
|
this.enableEdit().newHole(event.latlng)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -79,7 +79,7 @@ export class FeatureUpdater extends BaseUpdater {
|
||||||
id,
|
id,
|
||||||
feature,
|
feature,
|
||||||
})
|
})
|
||||||
datalayer.addLayer(feature)
|
datalayer.addFeature(feature)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update a property of an object
|
// Update a property of an object
|
||||||
|
|
|
@ -155,7 +155,6 @@ U.AddPolygonShapeAction = U.AddPolylineShapeAction.extend({
|
||||||
|
|
||||||
U.BaseFeatureAction = L.ToolbarAction.extend({
|
U.BaseFeatureAction = L.ToolbarAction.extend({
|
||||||
initialize: function (map, feature, latlng) {
|
initialize: function (map, feature, latlng) {
|
||||||
console.log("Toolbar init", latlng)
|
|
||||||
this.map = map
|
this.map = map
|
||||||
this.feature = feature
|
this.feature = feature
|
||||||
this.latlng = latlng
|
this.latlng = latlng
|
||||||
|
@ -183,8 +182,8 @@ U.CreateHoleAction = U.BaseFeatureAction.extend({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick: function (e) {
|
onClick: function (event) {
|
||||||
this.feature.startHole(e)
|
this.feature.ui.startHole(event)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -196,11 +195,11 @@ U.ToggleEditAction = U.BaseFeatureAction.extend({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
onClick: function (e) {
|
onClick: function (event) {
|
||||||
if (this.feature._toggleEditing) {
|
if (this.feature._toggleEditing) {
|
||||||
this.feature._toggleEditing(e) // Path
|
this.feature._toggleEditing(event) // Path
|
||||||
} else {
|
} else {
|
||||||
this.feature.edit(e) // Marker
|
this.feature.edit(event) // Marker
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1151,7 +1150,6 @@ U.Editable = L.Editable.extend({
|
||||||
// Leaflet.Editable will delete the drawn shape if invalid
|
// Leaflet.Editable will delete the drawn shape if invalid
|
||||||
// (eg. line has only one drawn point)
|
// (eg. line has only one drawn point)
|
||||||
// So let's check if the layer has no more shape
|
// So let's check if the layer has no more shape
|
||||||
console.log(event.layer.feature.coordinates, event.layer.feature.hasGeom())
|
|
||||||
if (!event.layer.feature.hasGeom()) {
|
if (!event.layer.feature.hasGeom()) {
|
||||||
event.layer.feature.del()
|
event.layer.feature.del()
|
||||||
} else {
|
} else {
|
||||||
|
@ -1169,7 +1167,6 @@ U.Editable = L.Editable.extend({
|
||||||
this.on('editable:editing', (event) => {
|
this.on('editable:editing', (event) => {
|
||||||
const layer = event.layer
|
const layer = event.layer
|
||||||
layer.feature.isDirty = true
|
layer.feature.isDirty = true
|
||||||
console.log('editing')
|
|
||||||
if (layer instanceof L.Marker) {
|
if (layer instanceof L.Marker) {
|
||||||
layer.feature.coordinates = layer._latlng
|
layer.feature.coordinates = layer._latlng
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1857,11 +1857,9 @@ U.Map = L.Map.extend({
|
||||||
if (feature._toggleEditing) feature._toggleEditing(event)
|
if (feature._toggleEditing) feature._toggleEditing(event)
|
||||||
else feature.edit(event)
|
else feature.edit(event)
|
||||||
}
|
}
|
||||||
} else {
|
} else if (!this.editTools?.drawing()) {
|
||||||
console.log('should show toolbar')
|
|
||||||
new L.Toolbar.Popup(event.latlng, {
|
new L.Toolbar.Popup(event.latlng, {
|
||||||
className: 'leaflet-inplace-toolbar',
|
className: 'leaflet-inplace-toolbar',
|
||||||
anchor: feature.getPopupToolbarAnchor(),
|
|
||||||
actions: feature.getInplaceToolbarActions(event),
|
actions: feature.getInplaceToolbarActions(event),
|
||||||
}).addTo(this, feature, event.latlng)
|
}).addTo(this, feature, event.latlng)
|
||||||
}
|
}
|
||||||
|
|
|
@ -187,7 +187,7 @@ def test_can_transfer_shape_from_simple_polyline(live_server, page, tilelayer):
|
||||||
map.click(position={"x": 100, "y": 200})
|
map.click(position={"x": 100, "y": 200})
|
||||||
expect(lines).to_have_count(1)
|
expect(lines).to_have_count(1)
|
||||||
|
|
||||||
# Draw another polygon
|
# Draw another line
|
||||||
page.get_by_title("Draw a polyline").click()
|
page.get_by_title("Draw a polyline").click()
|
||||||
map.click(position={"x": 250, "y": 250})
|
map.click(position={"x": 250, "y": 250})
|
||||||
map.click(position={"x": 200, "y": 250})
|
map.click(position={"x": 200, "y": 250})
|
||||||
|
@ -196,7 +196,7 @@ def test_can_transfer_shape_from_simple_polyline(live_server, page, tilelayer):
|
||||||
map.click(position={"x": 200, "y": 200})
|
map.click(position={"x": 200, "y": 200})
|
||||||
expect(lines).to_have_count(2)
|
expect(lines).to_have_count(2)
|
||||||
|
|
||||||
# Now that polygon 2 is selected, right click on first one
|
# Now that line 2 is selected, right click on first one
|
||||||
# and transfer shape
|
# and transfer shape
|
||||||
lines.first.click(position={"x": 10, "y": 1}, button="right")
|
lines.first.click(position={"x": 10, "y": 1}, button="right")
|
||||||
page.get_by_role("link", name="Transfer shape to edited feature").click()
|
page.get_by_role("link", name="Transfer shape to edited feature").click()
|
||||||
|
@ -235,18 +235,17 @@ def test_can_transfer_shape_from_multi(live_server, page, tilelayer, settings):
|
||||||
map.click(position={"x": 300, "y": 300})
|
map.click(position={"x": 300, "y": 300})
|
||||||
expect(lines).to_have_count(2)
|
expect(lines).to_have_count(2)
|
||||||
|
|
||||||
# Now that polygon 2 is selected, right click on first one
|
# Now that line 2 is selected, right click on first one
|
||||||
# and transfer shape
|
# and transfer shape
|
||||||
lines.first.click(position={"x": 10, "y": 1}, button="right")
|
lines.first.click(position={"x": 10, "y": 1}, button="right")
|
||||||
page.get_by_role("link", name="Transfer shape to edited feature").click()
|
page.get_by_role("link", name="Transfer shape to edited feature").click()
|
||||||
expect(lines).to_have_count(2)
|
expect(lines).to_have_count(2)
|
||||||
data = save_and_get_json(page)
|
data = save_and_get_json(page)
|
||||||
# FIXME this should be a LineString, not MultiLineString
|
|
||||||
assert data["features"][0]["geometry"] == {
|
assert data["features"][0]["geometry"] == {
|
||||||
"coordinates": [
|
"coordinates": [
|
||||||
[[-6.569824, 52.49616], [-7.668457, 52.49616], [-7.668457, 53.159947]]
|
[-6.569824, 52.49616], [-7.668457, 52.49616], [-7.668457, 53.159947]
|
||||||
],
|
],
|
||||||
"type": "MultiLineString",
|
"type": "LineString",
|
||||||
}
|
}
|
||||||
assert data["features"][1]["geometry"] == {
|
assert data["features"][1]["geometry"] == {
|
||||||
"coordinates": [
|
"coordinates": [
|
||||||
|
|
Loading…
Reference in a new issue