mirror of
https://github.com/umap-project/umap.git
synced 2025-04-28 19:42:36 +02:00
wip: first step in moving features to modules (work in progress)
This commit is contained in:
parent
f196bda25f
commit
7aa07709b3
12 changed files with 1579 additions and 95 deletions
1197
umap/static/umap/js/modules/data/features.js
Normal file
1197
umap/static/umap/js/modules/data/features.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,3 @@
|
|||
// Uses U.Marker, U.Polygon, U.Polyline, U.TableEditor not yet ES modules
|
||||
// Uses U.FormBuilder not available as ESM
|
||||
|
||||
// FIXME: this module should not depend on Leaflet
|
||||
|
@ -19,6 +18,7 @@ import {
|
|||
} from '../../components/alerts/alert.js'
|
||||
import { translate } from '../i18n.js'
|
||||
import { DataLayerPermissions } from '../permissions.js'
|
||||
import { Point, LineString, Polygon } from './features.js'
|
||||
|
||||
export const LAYER_TYPES = [DefaultLayer, Cluster, Heat, Choropleth, Categorized]
|
||||
|
||||
|
@ -32,7 +32,7 @@ export class DataLayer {
|
|||
this.map = map
|
||||
this.sync = map.sync_engine.proxy(this)
|
||||
this._index = Array()
|
||||
this._layers = {}
|
||||
this._features = {}
|
||||
this._geojson = null
|
||||
this._propertiesIndex = []
|
||||
this._loaded = false // Are layer metadata loaded
|
||||
|
@ -188,23 +188,14 @@ export class DataLayer {
|
|||
if (visible) this.map.removeLayer(this.layer)
|
||||
const Class = LAYER_MAP[this.options.type] || DefaultLayer
|
||||
this.layer = new Class(this)
|
||||
this.eachLayer(this.showFeature)
|
||||
this.eachFeature(this.showFeature)
|
||||
if (visible) this.show()
|
||||
this.propagateRemote()
|
||||
}
|
||||
|
||||
eachLayer(method, context) {
|
||||
for (const i in this._layers) {
|
||||
method.call(context || this, this._layers[i])
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
eachFeature(method, context) {
|
||||
if (this.isBrowsable()) {
|
||||
for (let i = 0; i < this._index.length; i++) {
|
||||
method.call(context || this, this._layers[this._index[i]])
|
||||
}
|
||||
for (const idx of this._index) {
|
||||
method.call(context || this, this._features[idx])
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
@ -254,7 +245,7 @@ export class DataLayer {
|
|||
|
||||
clear() {
|
||||
this.layer.clearLayers()
|
||||
this._layers = {}
|
||||
this._features = {}
|
||||
this._index = Array()
|
||||
if (this._geojson) {
|
||||
this.backupData()
|
||||
|
@ -268,13 +259,9 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
reindex() {
|
||||
const features = []
|
||||
this.eachFeature((feature) => features.push(feature))
|
||||
const features = Object.values(this._features)
|
||||
Utils.sortFeatures(features, this.map.getOption('sortKey'), L.lang)
|
||||
this._index = []
|
||||
for (let i = 0; i < features.length; i++) {
|
||||
this._index.push(stamp(features[i]))
|
||||
}
|
||||
this._index = features.map((feature) => stamp(feature))
|
||||
}
|
||||
|
||||
showAtZoom() {
|
||||
|
@ -371,28 +358,28 @@ export class DataLayer {
|
|||
|
||||
showFeature(feature) {
|
||||
if (feature.isFiltered()) return
|
||||
this.layer.addLayer(feature)
|
||||
this.layer.addLayer(feature.ui)
|
||||
}
|
||||
|
||||
addLayer(feature) {
|
||||
addFeature(feature) {
|
||||
const id = stamp(feature)
|
||||
feature.connectToDataLayer(this)
|
||||
this._index.push(id)
|
||||
this._layers[id] = feature
|
||||
this._features[id] = feature
|
||||
this.indexProperties(feature)
|
||||
this.map.features_index[feature.getSlug()] = feature
|
||||
this.showFeature(feature)
|
||||
if (this.hasDataLoaded()) this.dataChanged()
|
||||
}
|
||||
|
||||
removeLayer(feature, sync) {
|
||||
removeFeature(feature, sync) {
|
||||
const id = stamp(feature)
|
||||
if (sync !== false) feature.sync.delete()
|
||||
this.layer.removeLayer(feature)
|
||||
// if (sync !== false) feature.sync.delete()
|
||||
this.layer.removeLayer(feature.ui)
|
||||
delete this.map.features_index[feature.getSlug()]
|
||||
feature.disconnectFromDataLayer(this)
|
||||
this._index.splice(this._index.indexOf(id), 1)
|
||||
delete this._layers[id]
|
||||
delete this.map.features_index[feature.getSlug()]
|
||||
delete this._features[id]
|
||||
if (this.hasDataLoaded() && this.isVisible()) this.dataChanged()
|
||||
}
|
||||
|
||||
|
@ -416,7 +403,7 @@ export class DataLayer {
|
|||
}
|
||||
|
||||
sortedValues(property) {
|
||||
return Object.values(this._layers)
|
||||
return Object.values(this._features)
|
||||
.map((feature) => feature.properties[property])
|
||||
.filter((val, idx, arr) => arr.indexOf(val) === idx)
|
||||
.sort(Utils.naturalSort)
|
||||
|
@ -454,7 +441,7 @@ export class DataLayer {
|
|||
|
||||
const feature = this.geoJSONToLeaflet({ geometry, geojson })
|
||||
if (feature) {
|
||||
this.addLayer(feature)
|
||||
this.addFeature(feature)
|
||||
if (sync) feature.onCommit()
|
||||
return feature
|
||||
}
|
||||
|
@ -497,7 +484,8 @@ export class DataLayer {
|
|||
feature.setLatLng(latlng)
|
||||
return feature
|
||||
}
|
||||
return this._pointToLayer(geojson, latlng, id)
|
||||
return new Point(this, geojson)
|
||||
// return this._pointToLayer(geojson, latlng, id)
|
||||
|
||||
case 'MultiLineString':
|
||||
case 'LineString':
|
||||
|
@ -510,7 +498,8 @@ export class DataLayer {
|
|||
feature.setLatLngs(latlngs)
|
||||
return feature
|
||||
}
|
||||
return this._lineToLayer(geojson, latlngs, id)
|
||||
return new LineString(this, geojson)
|
||||
// return this._lineToLayer(geojson, latlngs, id)
|
||||
|
||||
case 'MultiPolygon':
|
||||
case 'Polygon':
|
||||
|
@ -519,7 +508,8 @@ export class DataLayer {
|
|||
feature.setLatLngs(latlngs)
|
||||
return feature
|
||||
}
|
||||
return this._polygonToLayer(geojson, latlngs, id)
|
||||
return new Polygon(this, geojson)
|
||||
// return this._polygonToLayer(geojson, latlngs, id)
|
||||
case 'GeometryCollection':
|
||||
return this.geojsonToFeatures(geometry.geometries)
|
||||
|
||||
|
@ -966,7 +956,7 @@ export class DataLayer {
|
|||
|
||||
featuresToGeoJSON() {
|
||||
const features = []
|
||||
this.eachLayer((layer) => features.push(layer.toGeoJSON()))
|
||||
this.eachFeature((feature) => features.push(feature.toGeoJSON()))
|
||||
return features
|
||||
}
|
||||
|
||||
|
@ -1031,19 +1021,21 @@ export class DataLayer {
|
|||
getFeatureByIndex(index) {
|
||||
if (index === -1) index = this._index.length - 1
|
||||
const id = this._index[index]
|
||||
return this._layers[id]
|
||||
return this._features[id]
|
||||
}
|
||||
|
||||
// TODO Add an index
|
||||
// For now, iterate on all the features.
|
||||
getFeatureById(id) {
|
||||
return Object.values(this._layers).find((feature) => feature.id === id)
|
||||
return Object.values(this._features).find((feature) => feature.id === id)
|
||||
}
|
||||
|
||||
getNextFeature(feature) {
|
||||
const id = this._index.indexOf(stamp(feature))
|
||||
const nextId = this._index[id + 1]
|
||||
return nextId ? this._layers[nextId] : this.getNextBrowsable().getFeatureByIndex(0)
|
||||
return nextId
|
||||
? this._features[nextId]
|
||||
: this.getNextBrowsable().getFeatureByIndex(0)
|
||||
}
|
||||
|
||||
getPreviousFeature(feature) {
|
||||
|
@ -1053,7 +1045,7 @@ export class DataLayer {
|
|||
const id = this._index.indexOf(stamp(feature))
|
||||
const previousId = this._index[id - 1]
|
||||
return previousId
|
||||
? this._layers[previousId]
|
||||
? this._features[previousId]
|
||||
: this.getPreviousBrowsable().getFeatureByIndex(-1)
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@ import URLs from './urls.js'
|
|||
import * as Utils from './utils.js'
|
||||
import { DataLayer, LAYER_TYPES } from './data/layer.js'
|
||||
import { DataLayerPermissions, MapPermissions } from './permissions.js'
|
||||
import { Point, LineString, Polygon } from './data/features.js'
|
||||
import { LeafletMarker, LeafletPolyline, LeafletPolygon } from './rendering/ui.js'
|
||||
|
||||
// Import modules and export them to the global scope.
|
||||
// For the not yet module-compatible JS out there.
|
||||
|
@ -52,10 +54,16 @@ window.U = {
|
|||
HTTPError,
|
||||
Importer,
|
||||
LAYER_TYPES,
|
||||
LeafletMarker,
|
||||
LeafletPolygon,
|
||||
LeafletPolyline,
|
||||
LineString,
|
||||
MapPermissions,
|
||||
NOKError,
|
||||
Orderable,
|
||||
Panel,
|
||||
Point,
|
||||
Polygon,
|
||||
Request,
|
||||
RequestError,
|
||||
Rules,
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
// Uses global L.HeatLayer, not exposed as ESM
|
||||
import { Marker, LatLng, latLngBounds, Bounds, point } from '../../../../vendors/leaflet/leaflet-src.esm.js'
|
||||
import {
|
||||
Marker,
|
||||
LatLng,
|
||||
latLngBounds,
|
||||
Bounds,
|
||||
point,
|
||||
} from '../../../../vendors/leaflet/leaflet-src.esm.js'
|
||||
import { LayerMixin } from './base.js'
|
||||
import * as Utils from '../../utils.js'
|
||||
import { translate } from '../../i18n.js'
|
||||
|
@ -27,7 +33,7 @@ export const Heat = L.HeatLayer.extend({
|
|||
let alt
|
||||
if (this.datalayer.options.heat?.intensityProperty) {
|
||||
alt = Number.parseFloat(
|
||||
layer.properties[this.datalayer.options.heat.intensityProperty || 0]
|
||||
layer.feature.properties[this.datalayer.options.heat.intensityProperty || 0]
|
||||
)
|
||||
latlng = new LatLng(latlng.lat, latlng.lng, alt)
|
||||
}
|
||||
|
|
|
@ -56,8 +56,8 @@ const RelativeColorLayerMixin = {
|
|||
|
||||
getValues: function () {
|
||||
const values = []
|
||||
this.datalayer.eachLayer((layer) => {
|
||||
const value = this._getValue(layer)
|
||||
this.datalayer.eachFeature((feature) => {
|
||||
const value = this._getValue(feature)
|
||||
if (value !== undefined) values.push(value)
|
||||
})
|
||||
return values
|
||||
|
|
215
umap/static/umap/js/modules/rendering/ui.js
Normal file
215
umap/static/umap/js/modules/rendering/ui.js
Normal file
|
@ -0,0 +1,215 @@
|
|||
import {
|
||||
Marker,
|
||||
Polyline,
|
||||
Polygon,
|
||||
DomUtil,
|
||||
} from '../../../vendors/leaflet/leaflet-src.esm.js'
|
||||
import { translate } from '../i18n.js'
|
||||
|
||||
const FeatureMixin = {
|
||||
initialize: function (feature) {
|
||||
this.feature = feature
|
||||
this.parentClass.prototype.initialize.call(this, this.feature.coordinates)
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
this.addInteractions()
|
||||
return this.parentClass.prototype.onAdd.call(this, map)
|
||||
},
|
||||
|
||||
onRemove: function (map) {
|
||||
this.parentClass.prototype.onRemove.call(this, map)
|
||||
if (map.editedFeature === this.feature) {
|
||||
this.feature._marked_for_deletion = true
|
||||
this.feature.endEdit()
|
||||
map.editPanel.close()
|
||||
}
|
||||
},
|
||||
|
||||
addInteractions: function () {
|
||||
this.on('contextmenu editable:vertex:contextmenu', this.feature._showContextMenu, this.feature)
|
||||
},
|
||||
|
||||
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)
|
||||
},
|
||||
}
|
||||
|
||||
export const LeafletMarker = Marker.extend({
|
||||
parentClass: Marker,
|
||||
includes: [FeatureMixin],
|
||||
|
||||
initialize: function (feature) {
|
||||
FeatureMixin.initialize.call(this, feature)
|
||||
this.setIcon(this.getIcon())
|
||||
},
|
||||
|
||||
onCommit: 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('editable:drawing:commit', this.onCommit)
|
||||
if (!this.feature.isReadOnly()) this.on('mouseover', this._enableDragging)
|
||||
this.on('mouseout', this._onMouseOut)
|
||||
this._popupHandlersAdded = true // prevent Leaflet from binding event on bindPopup
|
||||
this.on('popupopen', this.highlight)
|
||||
this.on('popupclose', this.resetHighlight)
|
||||
},
|
||||
|
||||
_onMouseOut: function () {
|
||||
if (this.dragging?._draggable && !this.dragging._draggable._moving) {
|
||||
// Do not disable if the mouse went out while dragging
|
||||
this._disableDragging()
|
||||
}
|
||||
},
|
||||
|
||||
_enableDragging: function () {
|
||||
// TODO: start dragging after 1 second on mouse down
|
||||
if (this._map.editEnabled) {
|
||||
if (!this.editEnabled()) this.enableEdit()
|
||||
// Enabling dragging on the marker override the Draggable._OnDown
|
||||
// event, which, as it stopPropagation, refrain the call of
|
||||
// _onDown with map-pane element, which is responsible to
|
||||
// set the _moved to false, and thus to enable the click.
|
||||
// We should find a cleaner way to handle this.
|
||||
this._map.dragging._draggable._moved = false
|
||||
}
|
||||
},
|
||||
|
||||
_disableDragging: function () {
|
||||
if (this._map.editEnabled) {
|
||||
if (this.editor?.drawing) return // when creating a new marker, the mouse can trigger the mouseover/mouseout event
|
||||
// do not listen to them
|
||||
this.disableEdit()
|
||||
}
|
||||
},
|
||||
|
||||
_initIcon: function () {
|
||||
this.options.icon = this.getIcon()
|
||||
Marker.prototype._initIcon.call(this)
|
||||
// Allow to run code when icon is actually part of the DOM
|
||||
this.options.icon.onAdd()
|
||||
// this.resetTooltip()
|
||||
},
|
||||
|
||||
getIconClass: function () {
|
||||
return this.feature.getOption('iconClass')
|
||||
},
|
||||
|
||||
getIcon: function () {
|
||||
const Class = U.Icon[this.getIconClass()] || U.Icon.Default
|
||||
return new Class({ feature: this.feature })
|
||||
},
|
||||
|
||||
_getTooltipAnchor: function () {
|
||||
const anchor = this.options.icon.options.tooltipAnchor.clone()
|
||||
const direction = this.getOption('labelDirection')
|
||||
if (direction === 'left') {
|
||||
anchor.x *= -1
|
||||
} else if (direction === 'bottom') {
|
||||
anchor.x = 0
|
||||
anchor.y = 0
|
||||
} else if (direction === 'top') {
|
||||
anchor.x = 0
|
||||
}
|
||||
return anchor
|
||||
},
|
||||
|
||||
_redraw: function () {
|
||||
this._initIcon()
|
||||
this.update()
|
||||
},
|
||||
|
||||
getCenter: function () {
|
||||
return this._latlng
|
||||
},
|
||||
})
|
||||
|
||||
const PathMixin = {
|
||||
_onMouseOver: function () {
|
||||
if (this._map.measureTools?.enabled()) {
|
||||
this._map.tooltip.open({ content: this.feature.getMeasure(), anchor: this })
|
||||
} else if (this._map.editEnabled && !this._map.editedFeature) {
|
||||
this._map.tooltip.open({ content: translate('Click to edit'), anchor: this })
|
||||
}
|
||||
},
|
||||
|
||||
onCommit: function () {
|
||||
this.feature.coordinates = this._latlngs
|
||||
this.feature.onCommit()
|
||||
},
|
||||
|
||||
addInteractions: function () {
|
||||
FeatureMixin.addInteractions.call(this)
|
||||
this.on('editable:disable', this.onCommit)
|
||||
this.on('mouseover', this._onMouseOver)
|
||||
this.on('drag editable:drag', this._onDrag)
|
||||
this.on('popupopen', this.highlightPath)
|
||||
this.on('popupclose', this._redraw)
|
||||
},
|
||||
|
||||
highlightPath: function () {
|
||||
this.parentClass.prototype.setStyle.call(this, {
|
||||
fillOpacity: Math.sqrt(this.feature.getDynamicOption('fillOpacity', 1.0)),
|
||||
opacity: 1.0,
|
||||
weight: 1.3 * this.feature.getDynamicOption('weight'),
|
||||
})
|
||||
},
|
||||
|
||||
_onDrag: function () {
|
||||
this.feature.coordinates = this._latlngs
|
||||
if (this._tooltip) this._tooltip.setLatLng(this.getCenter())
|
||||
},
|
||||
|
||||
onAdd: function (map) {
|
||||
this._container = null
|
||||
this.setStyle()
|
||||
FeatureMixin.onAdd.call(this, map)
|
||||
if (this.editing?.enabled()) this.editing.addHooks()
|
||||
// this.resetTooltip()
|
||||
this._path.dataset.feature = this.feature.id
|
||||
},
|
||||
|
||||
onRemove: function (map) {
|
||||
if (this.editing?.enabled()) this.editing.removeHooks()
|
||||
FeatureMixin.onRemove.call(this, map)
|
||||
},
|
||||
|
||||
setStyle: function (options = {}) {
|
||||
for (const option of this.feature.getStyleOptions()) {
|
||||
options[option] = this.feature.getDynamicOption(option)
|
||||
}
|
||||
options.pointerEvents = options.interactive ? 'visiblePainted' : 'stroke'
|
||||
this.parentClass.prototype.setStyle.call(this, options)
|
||||
},
|
||||
|
||||
_redraw: function () {
|
||||
this.setStyle()
|
||||
// this.resetTooltip()
|
||||
},
|
||||
}
|
||||
|
||||
export const LeafletPolyline = Polyline.extend({
|
||||
parentClass: Polyline,
|
||||
includes: [FeatureMixin, PathMixin],
|
||||
})
|
||||
|
||||
export const LeafletPolygon = Polygon.extend({
|
||||
parentClass: Polygon,
|
||||
includes: [FeatureMixin, PathMixin],
|
||||
})
|
|
@ -89,15 +89,15 @@ export default class TableEditor extends WithTemplate {
|
|||
const bounds = this.map.getBounds()
|
||||
const inBbox = this.map.browser.options.inBbox
|
||||
let html = ''
|
||||
for (const feature of Object.values(this.datalayer._layers)) {
|
||||
if (feature.isFiltered()) continue
|
||||
if (inBbox && !feature.isOnScreen(bounds)) continue
|
||||
this.datalayer.eachFeature((feature) => {
|
||||
if (feature.isFiltered()) return
|
||||
if (inBbox && !feature.isOnScreen(bounds)) return
|
||||
const tds = this.properties.map(
|
||||
(prop) =>
|
||||
`<td tabindex="0" data-property="${prop}">${feature.properties[prop] || ''}</td>`
|
||||
)
|
||||
html += `<tr data-feature="${feature.id}"><th><input type="checkbox" /></th>${tds.join('')}</tr>`
|
||||
}
|
||||
})
|
||||
this.elements.body.innerHTML = html
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ export default class TableEditor extends WithTemplate {
|
|||
.prompt(translate('Please enter the new name of this property'))
|
||||
.then(({ prompt }) => {
|
||||
if (!prompt || !this.validateName(prompt)) return
|
||||
this.datalayer.eachLayer((feature) => {
|
||||
this.datalayer.eachFeature((feature) => {
|
||||
feature.renameProperty(property, prompt)
|
||||
})
|
||||
this.datalayer.deindexProperty(property)
|
||||
|
@ -140,7 +140,7 @@ export default class TableEditor extends WithTemplate {
|
|||
translate('Are you sure you want to delete this property on all the features?')
|
||||
)
|
||||
.then(() => {
|
||||
this.datalayer.eachLayer((feature) => {
|
||||
this.datalayer.eachFeature((feature) => {
|
||||
feature.deleteProperty(property)
|
||||
})
|
||||
this.datalayer.deindexProperty(property)
|
||||
|
|
|
@ -142,7 +142,7 @@ U.AddPolylineShapeAction = U.BaseAction.extend({
|
|||
},
|
||||
|
||||
addHooks: function () {
|
||||
this.map.editedFeature.editor.newShape()
|
||||
this.map.editedFeature.ui.editor.newShape()
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -155,6 +155,7 @@ 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
|
||||
|
@ -310,7 +311,7 @@ U.DrawToolbar = L.Toolbar.Control.extend({
|
|||
}
|
||||
if (this.map.options.enablePolylineDraw) {
|
||||
this.options.actions.push(U.DrawPolylineAction)
|
||||
if (this.map.editedFeature && this.map.editedFeature instanceof U.Polyline) {
|
||||
if (this.map.editedFeature && this.map.editedFeature instanceof U.LineString) {
|
||||
this.options.actions.push(U.AddPolylineShapeAction)
|
||||
}
|
||||
}
|
||||
|
@ -1145,56 +1146,81 @@ U.Editable = L.Editable.extend({
|
|||
initialize: function (map, options) {
|
||||
L.Editable.prototype.initialize.call(this, map, options)
|
||||
this.on('editable:drawing:click editable:drawing:move', this.drawingTooltip)
|
||||
this.on('editable:drawing:end', (e) => {
|
||||
this.on('editable:drawing:end', (event) => {
|
||||
this.map.tooltip.close()
|
||||
// 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
|
||||
if (!e.layer.hasGeom()) e.layer.del()
|
||||
else e.layer.edit()
|
||||
})
|
||||
// Layer for items added by users
|
||||
this.on('editable:drawing:cancel', (e) => {
|
||||
if (e.layer instanceof U.Marker) e.layer.del()
|
||||
})
|
||||
this.on('editable:drawing:commit', function (e) {
|
||||
e.layer.isDirty = true
|
||||
if (this.map.editedFeature !== e.layer) e.layer.edit(e)
|
||||
})
|
||||
this.on('editable:editing', (e) => {
|
||||
const layer = e.layer
|
||||
layer.isDirty = true
|
||||
if (layer._tooltip && layer.isTooltipOpen()) {
|
||||
layer._tooltip.setLatLng(layer.getCenter())
|
||||
layer._tooltip.update()
|
||||
console.log(event.layer.feature.coordinates, event.layer.feature.hasGeom())
|
||||
if (!event.layer.feature.hasGeom()) {
|
||||
event.layer.feature.del()
|
||||
} else {
|
||||
event.layer.feature.edit()
|
||||
}
|
||||
})
|
||||
this.on('editable:vertex:ctrlclick', (e) => {
|
||||
const index = e.vertex.getIndex()
|
||||
if (index === 0 || (index === e.vertex.getLastIndex() && e.vertex.continue))
|
||||
e.vertex.continue()
|
||||
// Layer for items added by users
|
||||
this.on('editable:drawing:cancel', (event) => {
|
||||
if (event.layer instanceof U.LeafletMarker) event.layer.feature.del()
|
||||
})
|
||||
this.on('editable:vertex:altclick', (e) => {
|
||||
if (e.vertex.editor.vertexCanBeDeleted(e.vertex)) e.vertex.delete()
|
||||
this.on('editable:drawing:commit', function (event) {
|
||||
event.layer.feature.isDirty = true
|
||||
if (this.map.editedFeature !== event.layer) event.layer.feature.edit(event)
|
||||
})
|
||||
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 {
|
||||
layer.feature.coordinates = layer._latlngs
|
||||
}
|
||||
// if (layer._tooltip && layer.isTooltipOpen()) {
|
||||
// layer._tooltip.setLatLng(layer.getCenter())
|
||||
// layer._tooltip.update()
|
||||
// }
|
||||
})
|
||||
this.on('editable:vertex:ctrlclick', (event) => {
|
||||
const index = event.vertex.getIndex()
|
||||
if (
|
||||
index === 0 ||
|
||||
(index === event.vertex.getLastIndex() && event.vertex.continue)
|
||||
)
|
||||
event.vertex.continue()
|
||||
})
|
||||
this.on('editable:vertex:altclick', (event) => {
|
||||
if (event.vertex.editor.vertexCanBeDeleted(event.vertex)) event.vertex.delete()
|
||||
})
|
||||
this.on('editable:vertex:rawclick', this.onVertexRawClick)
|
||||
},
|
||||
|
||||
createPolyline: function (latlngs) {
|
||||
return new U.Polyline(this.map, latlngs, this._getDefaultProperties())
|
||||
const datalayer = this.map.defaultEditDataLayer()
|
||||
const point = new U.LineString(datalayer, {
|
||||
geometry: { type: 'LineString', coordinates: [] },
|
||||
})
|
||||
return point.ui
|
||||
},
|
||||
|
||||
createPolygon: function (latlngs) {
|
||||
return new U.Polygon(this.map, latlngs, this._getDefaultProperties())
|
||||
const datalayer = this.map.defaultEditDataLayer()
|
||||
const point = new U.Polygon(datalayer, {
|
||||
geometry: { type: 'Polygon', coordinates: [] },
|
||||
})
|
||||
return point.ui
|
||||
},
|
||||
|
||||
createMarker: function (latlng) {
|
||||
return new U.Marker(this.map, latlng, this._getDefaultProperties())
|
||||
const datalayer = this.map.defaultEditDataLayer()
|
||||
const point = new U.Point(datalayer, {
|
||||
geometry: { type: 'Point', coordinates: [latlng.lng, latlng.lat] },
|
||||
})
|
||||
return point.ui
|
||||
},
|
||||
|
||||
_getDefaultProperties: function () {
|
||||
const result = {}
|
||||
if (this.map.options.featuresHaveOwner && this.map.options.hasOwnProperty('user')) {
|
||||
if (this.map.options.featuresHaveOwner?.user) {
|
||||
result.geojson = { properties: { owner: this.map.options.user.id } }
|
||||
}
|
||||
return result
|
||||
|
@ -1203,7 +1229,7 @@ U.Editable = L.Editable.extend({
|
|||
connectCreatedToMap: function (layer) {
|
||||
// Overrided from Leaflet.Editable
|
||||
const datalayer = this.map.defaultEditDataLayer()
|
||||
datalayer.addLayer(layer)
|
||||
datalayer.addFeature(layer.feature)
|
||||
layer.isDirty = true
|
||||
return layer
|
||||
},
|
||||
|
@ -1231,7 +1257,7 @@ U.Editable = L.Editable.extend({
|
|||
} else {
|
||||
const tmpLatLngs = e.layer.editor._drawnLatLngs.slice()
|
||||
tmpLatLngs.push(e.latlng)
|
||||
measure = e.layer.getMeasure(tmpLatLngs)
|
||||
measure = e.layer.feature.getMeasure(tmpLatLngs)
|
||||
|
||||
if (e.layer.editor._drawnLatLngs.length < e.layer.editor.MIN_VERTEX) {
|
||||
// when drawing second point
|
||||
|
@ -1243,7 +1269,7 @@ U.Editable = L.Editable.extend({
|
|||
}
|
||||
} else {
|
||||
// when moving an existing point
|
||||
measure = e.layer.getMeasure()
|
||||
measure = e.layer.feature.getMeasure()
|
||||
}
|
||||
if (measure) {
|
||||
if (e.layer instanceof L.Polygon) {
|
||||
|
|
|
@ -360,7 +360,6 @@ L.FormBuilder.DataLayerSwitcher = L.FormBuilder.Select.extend({
|
|||
getOptions: function () {
|
||||
const options = []
|
||||
this.builder.map.eachDataLayerReverse((datalayer) => {
|
||||
console.log(datalayer.isLoaded(), datalayer.isDataReadOnly(), datalayer.isBrowsable())
|
||||
if (
|
||||
datalayer.isLoaded() &&
|
||||
!datalayer.isDataReadOnly() &&
|
||||
|
|
|
@ -2,11 +2,10 @@ U.Icon = L.DivIcon.extend({
|
|||
statics: {
|
||||
RECENT: [],
|
||||
},
|
||||
initialize: function (map, options) {
|
||||
this.map = map
|
||||
initialize: function (options) {
|
||||
const default_options = {
|
||||
iconSize: null, // Made in css
|
||||
iconUrl: this.map.getDefaultOption('iconUrl'),
|
||||
iconUrl: U.SCHEMA.iconUrl.default,
|
||||
feature: null,
|
||||
}
|
||||
options = L.Util.extend({}, default_options, options)
|
||||
|
@ -40,13 +39,13 @@ U.Icon = L.DivIcon.extend({
|
|||
let color
|
||||
if (this.feature) color = this.feature.getDynamicOption('color')
|
||||
else if (this.options.color) color = this.options.color
|
||||
else color = this.map.getDefaultOption('color')
|
||||
else color = U.SCHEMA.color.default
|
||||
return color
|
||||
},
|
||||
|
||||
_getOpacity: function () {
|
||||
if (this.feature) return this.feature.getOption('iconOpacity')
|
||||
return this.map.getDefaultOption('iconOpacity')
|
||||
return U.SCHEMA.iconOpacity.default
|
||||
},
|
||||
|
||||
formatUrl: (url, feature) =>
|
||||
|
@ -63,9 +62,9 @@ U.Icon.Default = U.Icon.extend({
|
|||
className: 'umap-div-icon',
|
||||
},
|
||||
|
||||
initialize: function (map, options) {
|
||||
initialize: function (options) {
|
||||
options = L.Util.extend({}, this.default_options, options)
|
||||
U.Icon.prototype.initialize.call(this, map, options)
|
||||
U.Icon.prototype.initialize.call(this, options)
|
||||
},
|
||||
|
||||
_setIconStyles: function (img, name) {
|
||||
|
@ -92,6 +91,7 @@ U.Icon.Default = U.Icon.extend({
|
|||
'icon_container',
|
||||
this.elements.main
|
||||
)
|
||||
this.elements.main.dataset.feature = this.feature?.id
|
||||
this.elements.arrow = L.DomUtil.create('div', 'icon_arrow', this.elements.main)
|
||||
const src = this._getIconUrl('icon')
|
||||
if (src) {
|
||||
|
@ -103,14 +103,14 @@ U.Icon.Default = U.Icon.extend({
|
|||
})
|
||||
|
||||
U.Icon.Circle = U.Icon.extend({
|
||||
initialize: function (map, options) {
|
||||
initialize: function (options) {
|
||||
const default_options = {
|
||||
popupAnchor: new L.Point(0, -6),
|
||||
tooltipAnchor: new L.Point(6, 0),
|
||||
className: 'umap-circle-icon',
|
||||
}
|
||||
options = L.Util.extend({}, default_options, options)
|
||||
U.Icon.prototype.initialize.call(this, map, options)
|
||||
U.Icon.prototype.initialize.call(this, options)
|
||||
},
|
||||
|
||||
_setIconStyles: function (img, name) {
|
||||
|
@ -124,6 +124,7 @@ U.Icon.Circle = U.Icon.extend({
|
|||
this.elements.main = L.DomUtil.create('div')
|
||||
this.elements.main.innerHTML = ' '
|
||||
this._setIconStyles(this.elements.main, 'icon')
|
||||
this.elements.main.dataset.feature = this.feature?.id
|
||||
return this.elements.main
|
||||
},
|
||||
})
|
||||
|
@ -153,6 +154,7 @@ U.Icon.Ball = U.Icon.Default.extend({
|
|||
'icon_container',
|
||||
this.elements.main
|
||||
)
|
||||
this.elements.main.dataset.feature = this.feature?.id
|
||||
this.elements.arrow = L.DomUtil.create('div', 'icon_arrow', this.elements.main)
|
||||
this._setIconStyles(this.elements.main, 'icon')
|
||||
return this.elements.main
|
||||
|
|
|
@ -193,7 +193,7 @@ U.Map = L.Map.extend({
|
|||
window.onbeforeunload = () => (this.editEnabled && this.isDirty) || null
|
||||
this.backup()
|
||||
this.initContextMenu()
|
||||
this.on('click contextmenu.show', this.closeInplaceToolbar)
|
||||
this.on('click', this.onClick)
|
||||
},
|
||||
|
||||
initSyncEngine: async function () {
|
||||
|
@ -863,7 +863,7 @@ U.Map = L.Map.extend({
|
|||
},
|
||||
|
||||
eachFeature: function (callback, context) {
|
||||
this.eachDataLayer((datalayer) => {
|
||||
this.eachBrowsableDataLayer((datalayer) => {
|
||||
if (datalayer.isVisible()) datalayer.eachFeature(callback, context)
|
||||
})
|
||||
},
|
||||
|
@ -1555,6 +1555,7 @@ U.Map = L.Map.extend({
|
|||
this.editPanel.close()
|
||||
this.fullPanel.close()
|
||||
this.sync.stop()
|
||||
this.closeInplaceToolbar()
|
||||
},
|
||||
|
||||
hasEditMode: function () {
|
||||
|
@ -1832,6 +1833,45 @@ U.Map = L.Map.extend({
|
|||
return url
|
||||
},
|
||||
|
||||
getFeatureById: function (id) {
|
||||
let feature
|
||||
for (const datalayer of Object.values(this.datalayers)) {
|
||||
feature = datalayer.getFeatureById(id)
|
||||
if (feature) return feature
|
||||
}
|
||||
},
|
||||
|
||||
onClick: function (event) {
|
||||
const container = event.originalEvent.target.closest('[data-feature]')
|
||||
if (container) {
|
||||
const feature = this.getFeatureById(container.dataset.feature)
|
||||
if (this.measureTools?.enabled()) return
|
||||
this._popupHandlersAdded = true // Prevent leaflet from managing event
|
||||
if (!this.editEnabled) {
|
||||
feature.view(event)
|
||||
} else if (!feature.isReadOnly()) {
|
||||
if (event.originalEvent.shiftKey) {
|
||||
if (event.originalEvent.ctrlKey || event.originalEvent.metaKey) {
|
||||
feature.datalayer.edit(event)
|
||||
} else {
|
||||
if (feature._toggleEditing) feature._toggleEditing(event)
|
||||
else feature.edit(event)
|
||||
}
|
||||
} else {
|
||||
console.log('should show toolbar')
|
||||
new L.Toolbar.Popup(event.latlng, {
|
||||
className: 'leaflet-inplace-toolbar',
|
||||
anchor: feature.getPopupToolbarAnchor(),
|
||||
actions: feature.getInplaceToolbarActions(event),
|
||||
}).addTo(this, feature, event.latlng)
|
||||
}
|
||||
}
|
||||
L.DomEvent.stop(event)
|
||||
} else {
|
||||
this.closeInplaceToolbar()
|
||||
}
|
||||
},
|
||||
|
||||
closeInplaceToolbar: function () {
|
||||
const toolbar = this._toolbars[L.Toolbar.Popup._toolbar_class_id]
|
||||
if (toolbar) toolbar.remove()
|
||||
|
|
|
@ -46,7 +46,6 @@
|
|||
<script src="{% static 'umap/js/umap.popup.js' %}" defer></script>
|
||||
<script src="{% static 'umap/js/umap.forms.js' %}" defer></script>
|
||||
<script src="{% static 'umap/js/umap.icon.js' %}" defer></script>
|
||||
<script src="{% static 'umap/js/umap.features.js' %}" defer></script>
|
||||
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
|
||||
<script src="{% static 'umap/js/umap.js' %}" defer></script>
|
||||
<script src="{% static 'umap/js/components/fragment.js' %}" defer></script>
|
||||
|
|
Loading…
Reference in a new issue