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.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
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
// },
|
||||
})
|
||||
|
|
|
@ -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.on('dragend', (event) => {
|
||||
this.isDirty = true
|
||||
this.feature.edit(e)
|
||||
this.feature.sync.update('geometry', this.getGeometry())
|
||||
},
|
||||
this
|
||||
)
|
||||
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)
|
||||
},
|
||||
})
|
||||
|
|
|
@ -79,7 +79,7 @@ export class FeatureUpdater extends BaseUpdater {
|
|||
id,
|
||||
feature,
|
||||
})
|
||||
datalayer.addLayer(feature)
|
||||
datalayer.addFeature(feature)
|
||||
}
|
||||
|
||||
// Update a property of an object
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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": [
|
||||
|
|
Loading…
Reference in a new issue