Merge pull request #1989 from umap-project/layer-to-modules

chore: move layers to modules/
This commit is contained in:
Yohan Boniface 2024-07-23 21:42:22 +02:00 committed by GitHub
commit ab34765c30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 2054 additions and 1965 deletions

2
.gitignore vendored
View file

@ -9,7 +9,7 @@ site/*
.pytest_cache/
node_modules
umap.conf
data
./data
./static
### Python ###

View file

@ -43,16 +43,14 @@ export default class Caption {
const p = DomUtil.create('p', 'datalayer-legend', container)
const legend = DomUtil.create('span', '', p)
const headline = DomUtil.create('strong', '', p)
datalayer.onceLoaded(() => {
datalayer.renderLegend(legend)
if (datalayer.options.description) {
DomUtil.element({
tagName: 'span',
parent: p,
safeHTML: Utils.toHTML(datalayer.options.description),
})
}
})
datalayer.renderLegend(legend)
if (datalayer.options.description) {
DomUtil.element({
tagName: 'span',
parent: p,
safeHTML: Utils.toHTML(datalayer.options.description),
})
}
datalayer.renderToolbox(headline)
DomUtil.add('span', '', headline, `${datalayer.options.name} `)
}

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,12 @@
import {
uMapAlert as Alert,
uMapAlertConflict as AlertConflict,
uMapAlertCreation as AlertCreation,
} from '../components/alerts/alert.js'
import { AjaxAutocomplete, AjaxAutocompleteMultiple, AutocompleteDatalist } from './autocomplete.js'
import {
AjaxAutocomplete,
AjaxAutocompleteMultiple,
AutocompleteDatalist,
} from './autocomplete.js'
import Browser from './browser.js'
import Caption from './caption.js'
import Facets from './facets.js'
@ -23,6 +26,7 @@ import TableEditor from './tableeditor.js'
import Tooltip from './ui/tooltip.js'
import URLs from './urls.js'
import * as Utils from './utils.js'
import { DataLayer, LAYER_TYPES } from './data/layer.js'
// Import modules and export them to the global scope.
// For the not yet module-compatible JS out there.
@ -31,12 +35,12 @@ import * as Utils from './utils.js'
window.U = {
Alert,
AlertCreation,
AlertConflict,
AjaxAutocomplete,
AjaxAutocompleteMultiple,
AutocompleteDatalist,
Browser,
Caption,
DataLayer,
Dialog,
EditPanel,
Facets,
@ -45,6 +49,7 @@ window.U = {
Help,
HTTPError,
Importer,
LAYER_TYPES,
NOKError,
Orderable,
Panel,

View file

@ -0,0 +1,105 @@
import { FeatureGroup, TileLayer } from '../../../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from '../../i18n.js'
import * as Utils from '../../utils.js'
export const LayerMixin = {
browsable: true,
onInit: function (map) {
if (this.datalayer.autoLoaded()) map.on('zoomend', this.onZoomEnd, this)
},
onDelete: function (map) {
map.off('zoomend', this.onZoomEnd, this)
},
onAdd: function (map) {
map.on('moveend', this.onMoveEnd, this)
},
onRemove: function (map) {
map.off('moveend', this.onMoveEnd, this)
},
getType: function () {
const proto = Object.getPrototypeOf(this)
return proto.constructor.TYPE
},
getName: function () {
const proto = Object.getPrototypeOf(this)
return proto.constructor.NAME
},
getFeatures: function () {
return this._layers
},
getEditableOptions: () => [],
onEdit: () => {},
hasDataVisible: function () {
return !!Object.keys(this._layers).length
},
// Called when data changed on the datalayer
dataChanged: () => {},
onMoveEnd: function () {
if (this.datalayer.isRemoteLayer() && this.datalayer.showAtZoom()) {
this.datalayer.fetchRemoteData()
}
},
onZoomEnd() {
if (this.datalayer._forcedVisibility) return
if (!this.datalayer.showAtZoom() && this.datalayer.isVisible()) {
this.datalayer.hide()
}
if (this.datalayer.showAtZoom() && !this.datalayer.isVisible()) {
this.datalayer.show()
}
},
}
export const Default = FeatureGroup.extend({
statics: {
NAME: translate('Default'),
TYPE: 'Default',
},
includes: [LayerMixin],
initialize: function (datalayer) {
this.datalayer = datalayer
FeatureGroup.prototype.initialize.call(this)
LayerMixin.onInit.call(this, this.datalayer.map)
},
onAdd: function (map) {
LayerMixin.onAdd.call(this, map)
return FeatureGroup.prototype.onAdd.call(this, map)
},
onRemove: function (map) {
LayerMixin.onRemove.call(this, map)
return FeatureGroup.prototype.onRemove.call(this, map)
},
})
TileLayer.include({
toJSON() {
return {
minZoom: this.options.minZoom,
maxZoom: this.options.maxZoom,
attribution: this.options.attribution,
url_template: this._url,
name: this.options.name,
tms: this.options.tms,
}
},
getAttribution() {
return Utils.toHTML(this.options.attribution)
},
})

View file

@ -0,0 +1,103 @@
// WARNING must be loaded dynamically, or at least after leaflet.markercluster
// Uses global L.MarkerCluster and L.MarkerClusterGroup, not exposed as ESM
// Uses global U.Icon not yet a module
import { translate } from '../../i18n.js'
import { LayerMixin } from './base.js'
import * as Utils from '../../utils.js'
const MarkerCluster = L.MarkerCluster.extend({
// Custom class so we can call computeTextColor
// when element is already on the DOM.
_initIcon: function () {
L.MarkerCluster.prototype._initIcon.call(this)
const div = this._icon.querySelector('div')
// Compute text color only when icon is added to the DOM.
div.style.color = this._iconObj.computeTextColor(div)
},
})
export const Cluster = L.MarkerClusterGroup.extend({
statics: {
NAME: translate('Clustered'),
TYPE: 'Cluster',
},
includes: [LayerMixin],
initialize: function (datalayer) {
this.datalayer = datalayer
if (!Utils.isObject(this.datalayer.options.cluster)) {
this.datalayer.options.cluster = {}
}
const options = {
polygonOptions: {
color: this.datalayer.getColor(),
},
iconCreateFunction: (cluster) => new U.Icon.Cluster(datalayer, cluster),
}
if (this.datalayer.options.cluster?.radius) {
options.maxClusterRadius = this.datalayer.options.cluster.radius
}
L.MarkerClusterGroup.prototype.initialize.call(this, options)
LayerMixin.onInit.call(this, this.datalayer.map)
this._markerCluster = MarkerCluster
this._layers = []
},
onAdd: function (map) {
LayerMixin.onAdd.call(this, map)
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
// and loading the map at a zoom outside of that zoom range.
// FIXME: move this upstream (_unbindEvents should accept a map parameter
// instead of relying on this._map)
this._map = map
LayerMixin.onRemove.call(this, map)
return L.MarkerClusterGroup.prototype.onRemove.call(this, map)
},
addLayer: function (layer) {
this._layers.push(layer)
return L.MarkerClusterGroup.prototype.addLayer.call(this, layer)
},
removeLayer: function (layer) {
this._layers.splice(this._layers.indexOf(layer), 1)
return L.MarkerClusterGroup.prototype.removeLayer.call(this, layer)
},
getEditableOptions: () => [
[
'options.cluster.radius',
{
handler: 'BlurIntInput',
placeholder: translate('Clustering radius'),
helpText: translate('Override clustering radius (default 80)'),
},
],
[
'options.cluster.textColor',
{
handler: 'TextColorPicker',
placeholder: translate('Auto'),
helpText: translate('Text color for the cluster label'),
},
],
],
onEdit: function (field, builder) {
if (field === 'options.cluster.radius') {
// No way to reset radius of an already instanciated MarkerClusterGroup...
this.datalayer.resetLayer(true)
return
}
if (field === 'options.color') {
this.options.polygonOptions.color = this.datalayer.getColor()
}
},
})

View file

@ -0,0 +1,176 @@
// Uses global L.HeatLayer, not exposed as ESM
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'
export const Heat = L.HeatLayer.extend({
statics: {
NAME: translate('Heatmap'),
TYPE: 'Heat',
},
includes: [LayerMixin],
browsable: false,
initialize: function (datalayer) {
this.datalayer = datalayer
L.HeatLayer.prototype.initialize.call(this, [], this.datalayer.options.heat)
LayerMixin.onInit.call(this, this.datalayer.map)
if (!Utils.isObject(this.datalayer.options.heat)) {
this.datalayer.options.heat = {}
}
},
addLayer: function (layer) {
if (layer instanceof Marker) {
let latlng = layer.getLatLng()
let alt
if (this.datalayer.options.heat?.intensityProperty) {
alt = Number.parseFloat(
layer.properties[this.datalayer.options.heat.intensityProperty || 0]
)
latlng = new LatLng(latlng.lat, latlng.lng, alt)
}
this.addLatLng(latlng)
}
},
onAdd: function (map) {
LayerMixin.onAdd.call(this, map)
return L.HeatLayer.prototype.onAdd.call(this, map)
},
onRemove: function (map) {
LayerMixin.onRemove.call(this, map)
return L.HeatLayer.prototype.onRemove.call(this, map)
},
clearLayers: function () {
this.setLatLngs([])
},
getFeatures: () => ({}),
getBounds: function () {
return latLngBounds(this._latlngs)
},
getEditableOptions: () => [
[
'options.heat.radius',
{
handler: 'Range',
min: 10,
max: 100,
step: 5,
label: translate('Heatmap radius'),
helpText: translate('Override heatmap radius (default 25)'),
},
],
[
'options.heat.intensityProperty',
{
handler: 'BlurInput',
placeholder: translate('Heatmap intensity property'),
helpText: translate('Optional intensity property for heatmap'),
},
],
],
onEdit: function (field, builder) {
if (field === 'options.heat.intensityProperty') {
this.datalayer.resetLayer(true) // We need to repopulate the latlngs
return
}
if (field === 'options.heat.radius') {
this.options.radius = this.datalayer.options.heat.radius
}
this._updateOptions()
},
redraw: function () {
// setlalngs call _redraw through setAnimFrame, thus async, so this
// can ends with race condition if we remove the layer very faslty after.
// TODO: PR in upstream Leaflet.heat
if (!this._map) return
L.HeatLayer.prototype.redraw.call(this)
},
_redraw: function () {
// Import patch from https://github.com/Leaflet/Leaflet.heat/pull/78
// Remove me when this get merged and released.
if (!this._map) {
return
}
const data = []
const r = this._heat._r
const size = this._map.getSize()
const bounds = new Bounds(point([-r, -r]), size.add([r, r]))
const cellSize = r / 2
const grid = []
const panePos = this._map._getMapPanePos()
const offsetX = panePos.x % cellSize
const offsetY = panePos.y % cellSize
let i
let len
let p
let cell
let x
let y
let j
let len2
this._max = 1
for (i = 0, len = this._latlngs.length; i < len; i++) {
p = this._map.latLngToContainerPoint(this._latlngs[i])
x = Math.floor((p.x - offsetX) / cellSize) + 2
y = Math.floor((p.y - offsetY) / cellSize) + 2
const alt =
this._latlngs[i].alt !== undefined
? this._latlngs[i].alt
: this._latlngs[i][2] !== undefined
? +this._latlngs[i][2]
: 1
grid[y] = grid[y] || []
cell = grid[y][x]
if (!cell) {
cell = grid[y][x] = [p.x, p.y, alt]
cell.p = p
} else {
cell[0] = (cell[0] * cell[2] + p.x * alt) / (cell[2] + alt) // x
cell[1] = (cell[1] * cell[2] + p.y * alt) / (cell[2] + alt) // y
cell[2] += alt // cumulated intensity value
}
// Set the max for the current zoom level
if (cell[2] > this._max) {
this._max = cell[2]
}
}
this._heat.max(this._max)
for (i = 0, len = grid.length; i < len; i++) {
if (grid[i]) {
for (j = 0, len2 = grid[i].length; j < len2; j++) {
cell = grid[i][j]
if (cell && bounds.contains(cell.p)) {
data.push([
Math.round(cell[0]),
Math.round(cell[1]),
Math.min(cell[2], this._max),
])
}
}
}
}
this._heat.data(data).draw(this.options.minOpacity)
this._frame = null
},
})

View file

@ -0,0 +1,364 @@
import { FeatureGroup, DomUtil } from '../../../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from '../../i18n.js'
import { LayerMixin } from './base.js'
import * as Utils from '../../utils.js'
// Layer where each feature color is relative to the others,
// so we need all features before behing able to set one
// feature layer
const RelativeColorLayerMixin = {
initialize: function (datalayer) {
this.datalayer = datalayer
this.colorSchemes = Object.keys(colorbrewer)
.filter((k) => k !== 'schemeGroups')
.sort()
const key = this.getType().toLowerCase()
if (!Utils.isObject(this.datalayer.options[key])) {
this.datalayer.options[key] = {}
}
FeatureGroup.prototype.initialize.call(this, [], this.datalayer.options[key])
LayerMixin.onInit.call(this, this.datalayer.map)
},
dataChanged: function () {
this.redraw()
},
redraw: function () {
this.compute()
if (this._map) this.eachLayer(this._map.addLayer, this._map)
},
getOption: function (option, feature) {
if (feature && option === feature.staticOptions.mainColor) {
return this.getColor(feature)
}
},
addLayer: function (layer) {
// Do not add yet the layer to the map
// wait for datachanged event, so we can compute breaks only once
const id = this.getLayerId(layer)
this._layers[id] = layer
return this
},
onAdd: function (map) {
this.compute()
LayerMixin.onAdd.call(this, map)
return FeatureGroup.prototype.onAdd.call(this, map)
},
onRemove: function (map) {
LayerMixin.onRemove.call(this, map)
return FeatureGroup.prototype.onRemove.call(this, map)
},
getValues: function () {
const values = []
this.datalayer.eachLayer((layer) => {
const value = this._getValue(layer)
if (value !== undefined) values.push(value)
})
return values
},
renderLegend: function (container) {
const parent = DomUtil.create('ul', '', container)
const items = this.getLegendItems()
for (const [color, label] of items) {
const li = DomUtil.create('li', '', parent)
const colorEl = DomUtil.create('span', 'datalayer-color', li)
colorEl.style.backgroundColor = color
const labelEl = DomUtil.create('span', '', li)
labelEl.textContent = label
}
},
getColorSchemes: function (classes) {
return this.colorSchemes.filter((scheme) => Boolean(colorbrewer[scheme][classes]))
},
}
export const Choropleth = FeatureGroup.extend({
statics: {
NAME: translate('Choropleth'),
TYPE: 'Choropleth',
},
includes: [LayerMixin, RelativeColorLayerMixin],
// Have defaults that better suit the choropleth mode.
defaults: {
color: 'white',
fillColor: 'red',
fillOpacity: 0.7,
weight: 2,
},
MODES: {
kmeans: translate('K-means'),
equidistant: translate('Equidistant'),
jenks: translate('Jenks-Fisher'),
quantiles: translate('Quantiles'),
manual: translate('Manual'),
},
_getValue: function (feature) {
const key = this.datalayer.options.choropleth.property || 'value'
const value = +feature.properties[key]
if (!Number.isNaN(value)) return value
},
compute: function () {
const values = this.getValues()
if (!values.length) {
this.options.breaks = []
this.options.colors = []
return
}
const mode = this.datalayer.options.choropleth.mode
let classes = +this.datalayer.options.choropleth.classes || 5
let breaks
classes = Math.min(classes, values.length)
if (mode === 'manual') {
const manualBreaks = this.datalayer.options.choropleth.breaks
if (manualBreaks) {
breaks = manualBreaks
.split(',')
.map((b) => +b)
.filter((b) => !Number.isNaN(b))
}
} else if (mode === 'equidistant') {
breaks = ss.equalIntervalBreaks(values, classes)
} else if (mode === 'jenks') {
breaks = ss.jenks(values, classes)
} else if (mode === 'quantiles') {
const quantiles = [...Array(classes)].map((e, i) => i / classes).concat(1)
breaks = ss.quantile(values, quantiles)
} else {
breaks = ss.ckmeans(values, classes).map((cluster) => cluster[0])
breaks.push(ss.max(values)) // Needed for computing the legend
}
this.options.breaks = breaks || []
this.datalayer.options.choropleth.breaks = this.options.breaks
.map((b) => +b.toFixed(2))
.join(',')
const fillColor = this.datalayer.getOption('fillColor') || this.defaults.fillColor
let colorScheme = this.datalayer.options.choropleth.brewer
if (!colorbrewer[colorScheme]) colorScheme = 'Blues'
this.options.colors = colorbrewer[colorScheme][this.options.breaks.length - 1] || []
},
getColor: function (feature) {
if (!feature) return // FIXME should not happen
const featureValue = this._getValue(feature)
// Find the bucket/step/limit that this value is less than and give it that color
for (let i = 1; i < this.options.breaks.length; i++) {
if (featureValue <= this.options.breaks[i]) {
return this.options.colors[i - 1]
}
}
},
onEdit: function (field, builder) {
// Only compute the breaks if we're dealing with choropleth
if (!field.startsWith('options.choropleth')) return
// If user touches the breaks, then force manual mode
if (field === 'options.choropleth.breaks') {
this.datalayer.options.choropleth.mode = 'manual'
if (builder) builder.helpers['options.choropleth.mode'].fetch()
}
this.compute()
// If user changes the mode or the number of classes,
// then update the breaks input value
if (field === 'options.choropleth.mode' || field === 'options.choropleth.classes') {
if (builder) builder.helpers['options.choropleth.breaks'].fetch()
}
},
getEditableOptions: function () {
return [
[
'options.choropleth.property',
{
handler: 'Select',
selectOptions: this.datalayer._propertiesIndex,
label: translate('Choropleth property value'),
},
],
[
'options.choropleth.brewer',
{
handler: 'Select',
label: translate('Choropleth color palette'),
selectOptions: this.colorSchemes,
},
],
[
'options.choropleth.classes',
{
handler: 'Range',
min: 3,
max: 9,
step: 1,
label: translate('Choropleth classes'),
helpText: translate('Number of desired classes (default 5)'),
},
],
[
'options.choropleth.breaks',
{
handler: 'BlurInput',
label: translate('Choropleth breakpoints'),
helpText: translate(
'Comma separated list of numbers, including min and max values.'
),
},
],
[
'options.choropleth.mode',
{
handler: 'MultiChoice',
default: 'kmeans',
choices: Object.entries(this.MODES),
label: translate('Choropleth mode'),
},
],
]
},
getLegendItems: function () {
return this.options.breaks.slice(0, -1).map((el, index) => {
const from = +this.options.breaks[index].toFixed(1)
const to = +this.options.breaks[index + 1].toFixed(1)
return [this.options.colors[index], `${from} - ${to}`]
})
},
})
export const Categorized = FeatureGroup.extend({
statics: {
NAME: translate('Categorized'),
TYPE: 'Categorized',
},
includes: [LayerMixin, RelativeColorLayerMixin],
MODES: {
manual: translate('Manual'),
alpha: translate('Alphabetical'),
},
defaults: {
color: 'white',
fillColor: 'red',
fillOpacity: 0.7,
weight: 2,
},
_getValue: function (feature) {
const key =
this.datalayer.options.categorized.property || this.datalayer._propertiesIndex[0]
return feature.properties[key]
},
getColor: function (feature) {
if (!feature) return // FIXME should not happen
const featureValue = this._getValue(feature)
for (let i = 0; i < this.options.categories.length; i++) {
if (featureValue === this.options.categories[i]) {
return this.options.colors[i]
}
}
},
compute: function () {
const values = this.getValues()
if (!values.length) {
this.options.categories = []
this.options.colors = []
return
}
const mode = this.datalayer.options.categorized.mode
let categories = []
if (mode === 'manual') {
const manualCategories = this.datalayer.options.categorized.categories
if (manualCategories) {
categories = manualCategories.split(',')
}
} else {
categories = values
.filter((val, idx, arr) => arr.indexOf(val) === idx)
.sort(Utils.naturalSort)
}
this.options.categories = categories
this.datalayer.options.categorized.categories = this.options.categories.join(',')
const fillColor = this.datalayer.getOption('fillColor') || this.defaults.fillColor
const colorScheme = this.datalayer.options.categorized.brewer
this._classes = this.options.categories.length
if (colorbrewer[colorScheme]?.[this._classes]) {
this.options.colors = colorbrewer[colorScheme][this._classes]
} else {
this.options.colors = colorbrewer?.Accent[this._classes]
? colorbrewer?.Accent[this._classes]
: U.COLORS // Fixme: move COLORS to modules/
}
},
getEditableOptions: function () {
return [
[
'options.categorized.property',
{
handler: 'Select',
selectOptions: this.datalayer._propertiesIndex,
label: translate('Category property'),
},
],
[
'options.categorized.brewer',
{
handler: 'Select',
label: translate('Color palette'),
selectOptions: this.getColorSchemes(this._classes),
},
],
[
'options.categorized.categories',
{
handler: 'BlurInput',
label: translate('Categories'),
helpText: translate('Comma separated list of categories.'),
},
],
[
'options.categorized.mode',
{
handler: 'MultiChoice',
default: 'alpha',
choices: Object.entries(this.MODES),
label: translate('Categories mode'),
},
],
]
},
onEdit: function (field, builder) {
// Only compute the categories if we're dealing with categorized
if (!field.startsWith('options.categorized')) return
// If user touches the categories, then force manual mode
if (field === 'options.categorized.categories') {
this.datalayer.options.categorized.mode = 'manual'
if (builder) builder.helpers['options.categorized.mode'].fetch()
}
this.compute()
// If user changes the mode
// then update the categories input value
if (field === 'options.categorized.mode') {
if (builder) builder.helpers['options.categorized.categories'].fetch()
}
},
getLegendItems: function () {
return this.options.categories.map((limit, index) => {
return [this.options.colors[index], this.options.categories[index]]
})
},
})

View file

@ -318,7 +318,7 @@ export default class TableEditor extends WithTemplate {
feature.del()
}
this.datalayer.show()
this.datalayer.fire('datachanged')
this.datalayer.dataChanged()
this.renderBody()
if (this.map.browser.isOpen()) {
this.map.browser.resetFilters()

View file

@ -562,107 +562,6 @@ L.Control.Embed = L.Control.Button.extend({
},
})
U.DataLayer.include({
renderLegend: function (container) {
if (this.layer.renderLegend) return this.layer.renderLegend(container)
const color = L.DomUtil.create('span', 'datalayer-color', container)
color.style.backgroundColor = this.getColor()
},
renderToolbox: function (container) {
const toggle = L.DomUtil.createButtonIcon(
container,
'icon-eye',
L._('Show/hide layer')
)
const zoomTo = L.DomUtil.createButtonIcon(
container,
'icon-zoom',
L._('Zoom to layer extent')
)
const edit = L.DomUtil.createButtonIcon(
container,
'icon-edit show-on-edit',
L._('Edit')
)
const table = L.DomUtil.createButtonIcon(
container,
'icon-table show-on-edit',
L._('Edit properties in a table')
)
const remove = L.DomUtil.createButtonIcon(
container,
'icon-delete show-on-edit',
L._('Delete layer')
)
if (this.isReadOnly()) {
L.DomUtil.addClass(container, 'readonly')
} else {
L.DomEvent.on(edit, 'click', this.edit, this)
L.DomEvent.on(table, 'click', this.tableEdit, this)
L.DomEvent.on(
remove,
'click',
function () {
if (!this.isVisible()) return
if (!confirm(L._('Are you sure you want to delete this layer?'))) return
this._delete()
},
this
)
}
L.DomEvent.on(toggle, 'click', this.toggle, this)
L.DomEvent.on(zoomTo, 'click', this.zoomTo, this)
container.classList.add(this.getHidableClass())
container.classList.toggle('off', !this.isVisible())
},
getHidableElements: function () {
return document.querySelectorAll(`.${this.getHidableClass()}`)
},
getHidableClass: function () {
return `show_with_datalayer_${L.stamp(this)}`
},
propagateDelete: function () {
const els = this.getHidableElements()
for (const el of els) {
L.DomUtil.remove(el)
}
},
propagateRemote: function () {
const els = this.getHidableElements()
for (const el of els) {
el.classList.toggle('remotelayer', this.isRemoteLayer())
}
},
propagateHide: function () {
const els = this.getHidableElements()
for (let i = 0; i < els.length; i++) {
L.DomUtil.addClass(els[i], 'off')
}
},
propagateShow: function () {
this.onceLoaded(function () {
const els = this.getHidableElements()
for (let i = 0; i < els.length; i++) {
L.DomUtil.removeClass(els[i], 'off')
}
}, this)
},
})
U.DataLayer.addInitHook(function () {
this.on('hide', this.propagateHide)
this.on('show', this.propagateShow)
this.on('erase', this.propagateDelete)
if (this.isVisible()) this.propagateShow()
})
const ControlsMixin = {
HIDDABLE_CONTROLS: [
'zoom',

View file

@ -342,14 +342,7 @@ L.FormBuilder.TextColorPicker = L.FormBuilder.ColorPicker.extend({
L.FormBuilder.LayerTypeChooser = L.FormBuilder.Select.extend({
getOptions: () => {
const layer_classes = [
U.Layer.Default,
U.Layer.Cluster,
U.Layer.Heat,
U.Layer.Choropleth,
U.Layer.Categorized,
]
return layer_classes.map((class_) => [class_.TYPE, class_.NAME])
return U.LAYER_TYPES.map((class_) => [class_.TYPE, class_.NAME])
},
})
@ -367,6 +360,7 @@ 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() &&

File diff suppressed because it is too large Load diff

View file

@ -5,6 +5,9 @@
<script type="module"
src="{% static 'umap/js/modules/leaflet-configure.js' %}"
defer></script>
<script src="{% static 'umap/vendors/markercluster/leaflet.markercluster.js' %}"
defer></script>
<script src="{% static 'umap/vendors/heat/leaflet-heat.js' %}" defer></script>
{% if locale %}
{% with "umap/locale/"|add:locale|add:".js" as path %}
<script src="{% static path %}" defer></script>
@ -21,14 +24,11 @@
<script src="{% static 'umap/vendors/csv2geojson/csv2geojson.js' %}" defer></script>
<script src="{% static 'umap/vendors/osmtogeojson/osmtogeojson.js' %}" defer></script>
<script src="{% static 'umap/vendors/loading/Control.Loading.js' %}" defer></script>
<script src="{% static 'umap/vendors/markercluster/leaflet.markercluster.js' %}"
defer></script>
<script src="{% static 'umap/vendors/contextmenu/leaflet.contextmenu.min.js' %}"
defer></script>
<script src="{% static 'umap/vendors/photon/leaflet.photon.js' %}" defer></script>
<script src="{% static 'umap/vendors/georsstogeojson/GeoRSSToGeoJSON.js' %}"
defer></script>
<script src="{% static 'umap/vendors/heat/leaflet-heat.js' %}" defer></script>
<script src="{% static 'umap/vendors/fullscreen/Leaflet.fullscreen.min.js' %}"
defer></script>
<script src="{% static 'umap/vendors/toolbar/leaflet.toolbar.js' %}" defer></script>
@ -49,7 +49,6 @@
<script src="{% static 'umap/js/umap.features.js' %}" defer></script>
<script src="{% static 'umap/js/umap.permissions.js' %}" defer></script>
<script src="{% static 'umap/js/umap.datalayer.permissions.js' %}" defer></script>
<script src="{% static 'umap/js/umap.layer.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>