wip: (almost) fix tests

This commit is contained in:
Yohan Boniface 2024-07-25 22:43:23 +02:00
parent 7aa07709b3
commit 081323dc8a
9 changed files with 355 additions and 339 deletions

View file

@ -23,6 +23,9 @@ class Feature {
this.properties = { _umap_options: {}, ...(geojson.properties || {}) }
this.staticOptions = {}
if (geojson.coordinates) {
geojson = { geometry: geojson }
}
if (geojson.geometry) {
this.populate(geojson)
}
@ -68,6 +71,10 @@ class Feature {
return this.ui.getCenter()
}
get bounds() {
return this.ui.getBounds()
}
getClassName() {
return this.staticOptions.className
}
@ -162,7 +169,7 @@ class Feature {
this.redraw()
}
edit(e) {
edit(event) {
if (!this.map.editEnabled || this.isReadOnly()) return
const container = DomUtil.create('div', 'umap-feature-container')
DomUtil.createTitle(
@ -176,7 +183,7 @@ class Feature {
[['datalayer', { handler: 'DataLayerSwitcher' }]],
{
callback() {
this.edit(e)
this.edit(event)
}, // removeLayer step will close the edit panel, let's reopen it
}
)
@ -207,11 +214,11 @@ class Feature {
builder.helpers['properties.name'].input.focus()
})
this.map.editedFeature = this
if (!this.isOnScreen()) this.zoomTo(e)
if (!this.isOnScreen()) this.zoomTo(event)
}
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())
})
}
@ -261,7 +268,7 @@ class Feature {
endEdit() {}
getDisplayName(fallback) {
if (fallback === undefined) fallback = this.datalayer.options.name
if (fallback === undefined) fallback = this.datalayer.getName()
const key = this.getOption('labelKey') || 'name'
// Variables mode.
if (U.Utils.hasVar(key))
@ -308,7 +315,8 @@ class Feature {
connectToDataLayer(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) {
@ -422,16 +430,12 @@ class Feature {
}
toGeoJSON() {
return {
return Utils.CopyJSON({
type: 'Feature',
geometry: this.geometry,
properties: this.cloneProperties(),
id: this.id,
}
}
getPopupToolbarAnchor() {
return [0, 0]
})
}
getInplaceToolbarActions() {
@ -442,73 +446,6 @@ class Feature {
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() {
const filterKeys = this.datalayer.getFilterKeys()
const filter = this.map.browser.options.filter
@ -519,7 +456,7 @@ class Feature {
matchFilter(filter, keys) {
filter = filter.toLowerCase()
if (U.Utils.hasVar(keys)) {
if (Utils.hasVar(keys)) {
return this.getDisplayName().toLowerCase().indexOf(filter) !== -1
}
keys = keys.split(',')
@ -552,10 +489,6 @@ class Feature {
return true
}
getVertexActions() {
return [U.DeleteVertexAction]
}
isMulti() {
return false
}
@ -578,7 +511,7 @@ class Feature {
if (L.lang) properties.lang = L.lang
properties.rank = this.getRank() + 1
properties.layer = this.datalayer.getName()
if (this.map && this.hasGeom()) {
if (this.ui._map && this.hasGeom()) {
const center = this.center
properties.lat = center.lat
properties.lon = center.lng
@ -600,13 +533,6 @@ class Feature {
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 {
@ -630,14 +556,6 @@ export class Point extends Feature {
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() {
return Boolean(this.coordinates)
}
@ -693,10 +611,6 @@ export class Point extends Feature {
bounds = bounds || this.map.getBounds()
return bounds.contains(this.coordinates)
}
// getPopupToolbarAnchor() {
// return this.options.icon.options.popupAnchor
// }
}
class Path extends Feature {
@ -704,10 +618,20 @@ class Path extends Feature {
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) {
super.connectToDataLayer(datalayer)
// 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) {
@ -717,17 +641,17 @@ class Path extends Feature {
}
}
_toggleEditing(e) {
_toggleEditing(event) {
if (this.map.editEnabled) {
if (this.ui.editEnabled()) {
this.endEdit()
this.map.editPanel.close()
} else {
this.edit(e)
this.edit(event)
}
}
// FIXME: disable when disabling global edit
L.DomEvent.stop(e)
L.DomEvent.stop(event)
}
getStyleOptions() {
@ -772,7 +696,7 @@ class Path extends Feature {
}
getBestZoom() {
return this.getOption('zoomTo') || this.map.getBoundsZoom(this.getBounds(), true)
return this.getOption('zoomTo') || this.map.getBoundsZoom(this.bounds, true)
}
endEdit() {
@ -781,100 +705,36 @@ class Path extends Feature {
}
transferShape(at, to) {
const shape = this.enableEdit().deleteShapeAt(at)
this.disableEdit()
const shape = this.ui.enableEdit().deleteShapeAt(at)
// FIXME: make Leaflet.Editable send an event instead
this.ui.geometryChanged()
this.ui.disableEdit()
if (!shape) return
to.enableEdit().appendShape(shape)
if (!this._latlngs.length || !this._latlngs[0].length) this.del()
to.ui.enableEdit().appendShape(shape)
to.ui.geometryChanged()
if (this.isEmpty()) this.del()
}
isolateShape(at) {
if (!this.isMulti()) return
const shape = this.enableEdit().deleteShapeAt(at)
this.disableEdit()
const shape = this.ui.enableEdit().deleteShapeAt(at)
this.ui.disableEdit()
if (!shape) return
const properties = this.cloneProperties()
const other = new (this instanceof U.Polyline ? U.Polyline : U.Polygon)(
this.map,
shape,
const other = new (this instanceof LineString ? LineString : Polygon)(
this.datalayer,
{
geojson: { properties },
properties,
geometry: this._toGeometry(shape),
}
)
this.datalayer.addLayer(other)
this.datalayer.addFeature(other)
other.edit()
return other
}
getContextMenuItems(e) {
let items = super.getContextMenuItems(e)
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)
getInplaceToolbarActions(event) {
const items = super.getInplaceToolbarActions(event)
if (this.isMulti()) {
items.push(U.DeleteShapeAction)
items.push(U.ExtractShapeFromMultiAction)
@ -884,16 +744,16 @@ class Path extends Feature {
isOnScreen(bounds) {
bounds = bounds || this.map.getBounds()
return bounds.overlaps(this.ui.getBounds())
return bounds.overlaps(this.bounds)
}
zoomTo({ easing, callback }) {
// Use bounds instead of centroid for paths.
easing = easing || this.map.getOption('easing')
if (easing) {
this.map.flyToBounds(this.getBounds(), this.getBestZoom())
this.map.flyToBounds(this.bounds, this.getBestZoom())
} 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)
}
@ -910,17 +770,22 @@ export class LineString extends Path {
}
}
get coordinates() {
_toLatlngs(geometry) {
return GeoJSON.coordsToLatLngs(
this.geometry.coordinates,
this.geometry.type === 'LineString' ? 0 : 1
geometry.coordinates,
geometry.type === 'LineString' ? 0 : 1
)
}
set coordinates(latlngs) {
const multi = !LineUtil.isFlat(latlngs)
this.geometry.coordinates = GeoJSON.latLngsToCoords(latlngs, multi ? 1 : 0, false)
this.geometry.type = multi ? 'MultiLineString' : 'LineString'
_toGeometry(latlngs) {
let multi = !LineUtil.isFlat(latlngs)
let coordinates = GeoJSON.latLngsToCoords(latlngs, multi ? 1 : 0, false)
if (coordinates.length === 1 && typeof coordinates[0][0] !== 'number') {
coordinates = Utils.flattenCoordinates(coordinates)
multi = false
}
const type = multi ? 'MultiLineString' : 'LineString'
return { coordinates, type }
}
isEmpty() {
@ -940,51 +805,11 @@ export class LineString extends Path {
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() {
const geojson = this.toGeoJSON()
geojson.geometry.type = 'Polygon'
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.
@ -1053,15 +878,6 @@ export class LineString extends Path {
isMulti() {
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 {
@ -1073,22 +889,22 @@ export class Polygon extends Path {
}
}
get coordinates() {
_toLatlngs(geometry) {
return GeoJSON.coordsToLatLngs(
this.geometry.coordinates,
this.geometry.type === 'Polygon' ? 1 : 2
geometry.coordinates,
geometry.type === 'Polygon' ? 1 : 2
)
}
set coordinates(latlngs) {
_toGeometry(latlngs) {
const holes = !LineUtil.isFlat(latlngs)
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) {
coords = [coords]
coordinates = [coordinates]
}
this.geometry.coordinates = coords
this.geometry.type = multi ? 'MultiPolygon' : 'Polygon'
const type = multi ? 'MultiPolygon' : 'Polygon'
return { coordinates, type }
}
isEmpty() {
@ -1133,35 +949,12 @@ export class Polygon extends Path {
return L.GeoUtil.readableArea(area, this.map.measureTools.getMeasureUnit())
}
getContextMenuEditItems(e) {
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() {
toLineString() {
const geojson = this.toGeoJSON()
delete geojson.id
delete geojson.properties.id
geojson.geometry.type = 'LineString'
geojson.geometry.coordinates = U.Utils.flattenCoordinates(
geojson.geometry.coordinates = Utils.flattenCoordinates(
geojson.geometry.coordinates
)
const polyline = this.datalayer.geojsonToFeatures(geojson)
@ -1171,17 +964,18 @@ export class Polygon extends Path {
getAdvancedEditActions(container) {
super.getAdvancedEditActions(container)
const toPolyline = DomUtil.createButton(
const toLineString = DomUtil.createButton(
'button umap-to-polyline',
container,
translate('Transform to lines'),
this.toPolyline,
this.toLineString,
this
)
}
isMulti() {
// Change me when Leaflet#3279 is merged.
// FIXME use TurfJS
return (
!LineUtil.isFlat(this.coordinates) &&
!LineUtil.isFlat(this.coordinates[0]) &&
@ -1189,8 +983,8 @@ export class Polygon extends Path {
)
}
getInplaceToolbarActions(e) {
const items = super.getInplaceToolbarActions(e)
getInplaceToolbarActions(event) {
const items = super.getInplaceToolbarActions(event)
items.push(U.CreateHoleAction)
return items
}

View file

@ -41,6 +41,7 @@ export class DataLayer {
this.parentPane = this.map.getPane('overlayPane')
this.pane = this.map.createPane(`datalayer${stamp(this)}`, this.parentPane)
this.pane.dataset.id = stamp(this)
// FIXME: should be on layer
this.renderer = L.svg({ pane: this.pane })
this.defaultOptions = {
displayOnLoad: true,
@ -481,11 +482,10 @@ export class DataLayer {
break
}
if (feature) {
feature.setLatLng(latlng)
feature.coordinates = latlng
return feature
}
return new Point(this, geojson)
// return this._pointToLayer(geojson, latlng, id)
case 'MultiLineString':
case 'LineString':
@ -495,21 +495,19 @@ export class DataLayer {
)
if (!latlngs.length) break
if (feature) {
feature.setLatLngs(latlngs)
feature.coordinates = latlngs
return feature
}
return new LineString(this, geojson)
// return this._lineToLayer(geojson, latlngs, id)
case 'MultiPolygon':
case 'Polygon':
latlngs = GeoJSON.coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2)
if (feature) {
feature.setLatLngs(latlngs)
feature.coordinates = latlngs
return feature
}
return new Polygon(this, geojson)
// return this._polygonToLayer(geojson, latlngs, id)
case 'GeometryCollection':
return this.geojsonToFeatures(geometry.geometries)
@ -900,9 +898,9 @@ export class DataLayer {
getOption(option, feature) {
if (this.layer?.getOption) {
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)
}
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.
// 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.options.labelKey) return this.options.labelKey
if (this.getOption('labelKey')) return this.getOption('labelKey')
if (this.map.options.sortKey) return this.map.options.sortKey
return 'name'
}

View file

@ -22,7 +22,7 @@ export const EXPORT_FORMATS = {
const table = []
map.eachFeature((feature) => {
const row = feature.toGeoJSON().properties
const center = feature.getCenter()
const center = feature.center
delete row._umap_options
row.Latitude = center.lat
row.Longitude = center.lng

View file

@ -4,6 +4,7 @@
import { translate } from '../../i18n.js'
import { LayerMixin } from './base.js'
import * as Utils from '../../utils.js'
import { Evented } from '../../../../vendors/leaflet/leaflet-src.esm.js'
const MarkerCluster = L.MarkerCluster.extend({
// 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)
},
onRemove: function (map) {
// In some situation, the onRemove is called before the layer is really
// 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()
}
},
// listens: function (type, propagate) {
// L.MarkerClusterGroup.prototype.listens.call(this, type, propagate)
// return Evented.prototype.listens.call(this, type, propagate)
// },
})

View file

@ -1,10 +1,14 @@
// Goes here all code related to Leaflet, DOM and user interactions.
import {
Marker,
Polyline,
Polygon,
DomUtil,
LineUtil,
} from '../../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from '../i18n.js'
import { uMapAlert as Alert } from '../../components/alerts/alert.js'
import * as Utils from '../utils.js'
const FeatureMixin = {
initialize: function (feature) {
@ -27,15 +31,88 @@ const FeatureMixin = {
},
addInteractions: function () {
this.on('contextmenu editable:vertex:contextmenu', this.feature._showContextMenu, this.feature)
this.on('contextmenu editable:vertex:contextmenu', this._showContextMenu)
},
onVertexRawClick: function (e) {
new L.Toolbar.Popup(e.latlng, {
className: 'leaflet-inplace-toolbar',
actions: this.getVertexActions(e),
}).addTo(this.map, this, e.latlng, e.vertex)
resetTooltip: function () {
if (!this.feature.hasGeom()) return
const displayName = this.feature.getDisplayName(null)
let showLabel = this.feature.getOption('showLabel')
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({
@ -47,22 +124,17 @@ export const LeafletMarker = Marker.extend({
this.setIcon(this.getIcon())
},
onCommit: function () {
geometryChanged: function() {
this.feature.coordinates = this._latlng
this.feature.onCommit()
},
addInteractions() {
FeatureMixin.addInteractions.call(this)
this.on(
'dragend',
function (e) {
this.isDirty = true
this.feature.edit(e)
this.feature.sync.update('geometry', this.getGeometry())
},
this
)
this.on('dragend', (event) => {
this.isDirty = true
this.feature.edit(event)
this.feature.sync.update('geometry', this.feature.getGeometry())
})
this.on('editable:drawing:commit', this.onCommit)
if (!this.feature.isReadOnly()) this.on('mouseover', this._enableDragging)
this.on('mouseout', this._onMouseOut)
@ -104,7 +176,7 @@ export const LeafletMarker = Marker.extend({
Marker.prototype._initIcon.call(this)
// Allow to run code when icon is actually part of the DOM
this.options.icon.onAdd()
// this.resetTooltip()
this.resetTooltip()
},
getIconClass: function () {
@ -118,7 +190,7 @@ export const LeafletMarker = Marker.extend({
_getTooltipAnchor: function () {
const anchor = this.options.icon.options.tooltipAnchor.clone()
const direction = this.getOption('labelDirection')
const direction = this.feature.getOption('labelDirection')
if (direction === 'left') {
anchor.x *= -1
} else if (direction === 'bottom') {
@ -138,6 +210,14 @@ export const LeafletMarker = Marker.extend({
getCenter: function () {
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 = {
@ -149,9 +229,8 @@ const PathMixin = {
}
},
onCommit: function () {
geometryChanged: function () {
this.feature.coordinates = this._latlngs
this.feature.onCommit()
},
addInteractions: function () {
@ -172,7 +251,7 @@ const PathMixin = {
},
_onDrag: function () {
this.feature.coordinates = this._latlngs
this.geometryChanged()
if (this._tooltip) this._tooltip.setLatLng(this.getCenter())
},
@ -181,7 +260,7 @@ const PathMixin = {
this.setStyle()
FeatureMixin.onAdd.call(this, map)
if (this.editing?.enabled()) this.editing.addHooks()
// this.resetTooltip()
this.resetTooltip()
this._path.dataset.feature = this.feature.id
},
@ -200,16 +279,162 @@ const PathMixin = {
_redraw: function () {
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({
parentClass: Polyline,
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({
parentClass: Polygon,
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)
},
})

View file

@ -79,7 +79,7 @@ export class FeatureUpdater extends BaseUpdater {
id,
feature,
})
datalayer.addLayer(feature)
datalayer.addFeature(feature)
}
// Update a property of an object

View file

@ -155,7 +155,6 @@ U.AddPolygonShapeAction = U.AddPolylineShapeAction.extend({
U.BaseFeatureAction = L.ToolbarAction.extend({
initialize: function (map, feature, latlng) {
console.log("Toolbar init", latlng)
this.map = map
this.feature = feature
this.latlng = latlng
@ -183,8 +182,8 @@ U.CreateHoleAction = U.BaseFeatureAction.extend({
},
},
onClick: function (e) {
this.feature.startHole(e)
onClick: function (event) {
this.feature.ui.startHole(event)
},
})
@ -196,11 +195,11 @@ U.ToggleEditAction = U.BaseFeatureAction.extend({
},
},
onClick: function (e) {
onClick: function (event) {
if (this.feature._toggleEditing) {
this.feature._toggleEditing(e) // Path
this.feature._toggleEditing(event) // Path
} 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
// (eg. line has only one drawn point)
// 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()) {
event.layer.feature.del()
} else {
@ -1169,7 +1167,6 @@ U.Editable = L.Editable.extend({
this.on('editable:editing', (event) => {
const layer = event.layer
layer.feature.isDirty = true
console.log('editing')
if (layer instanceof L.Marker) {
layer.feature.coordinates = layer._latlng
} else {

View file

@ -1857,11 +1857,9 @@ U.Map = L.Map.extend({
if (feature._toggleEditing) feature._toggleEditing(event)
else feature.edit(event)
}
} else {
console.log('should show toolbar')
} else if (!this.editTools?.drawing()) {
new L.Toolbar.Popup(event.latlng, {
className: 'leaflet-inplace-toolbar',
anchor: feature.getPopupToolbarAnchor(),
actions: feature.getInplaceToolbarActions(event),
}).addTo(this, feature, event.latlng)
}

View file

@ -187,7 +187,7 @@ def test_can_transfer_shape_from_simple_polyline(live_server, page, tilelayer):
map.click(position={"x": 100, "y": 200})
expect(lines).to_have_count(1)
# Draw another polygon
# Draw another line
page.get_by_title("Draw a polyline").click()
map.click(position={"x": 250, "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})
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
lines.first.click(position={"x": 10, "y": 1}, button="right")
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})
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
lines.first.click(position={"x": 10, "y": 1}, button="right")
page.get_by_role("link", name="Transfer shape to edited feature").click()
expect(lines).to_have_count(2)
data = save_and_get_json(page)
# FIXME this should be a LineString, not MultiLineString
assert data["features"][0]["geometry"] == {
"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"] == {
"coordinates": [