From 38d1a60917d3d486abf8827a611ed51d6e722f5b Mon Sep 17 00:00:00 2001 From: Thomas Halwax Date: Mon, 11 Dec 2023 11:13:16 +0100 Subject: [PATCH 01/73] supporting terrain-rgb tile services --- src/renderer/components/OSD.js | 2 +- .../properties/TilePresetProperties.js | 73 ++++++++++++------- .../properties/TileServiceProperties.js | 24 +++++- src/renderer/model/OSDDriver.js | 18 ++++- src/renderer/store/TileLayerStore.js | 36 +++++++-- src/renderer/store/documents/tile-service.js | 2 +- src/renderer/store/options/tile-service.js | 7 +- 7 files changed, 122 insertions(+), 40 deletions(-) diff --git a/src/renderer/components/OSD.js b/src/renderer/components/OSD.js index 38f977eb..618005e0 100644 --- a/src/renderer/components/OSD.js +++ b/src/renderer/components/OSD.js @@ -31,6 +31,6 @@ export const OSD = () => {
{ state.C2 }
-
+
{ state.C3 }
} diff --git a/src/renderer/components/properties/TilePresetProperties.js b/src/renderer/components/properties/TilePresetProperties.js index 69a5dd23..b14ca268 100644 --- a/src/renderer/components/properties/TilePresetProperties.js +++ b/src/renderer/components/properties/TilePresetProperties.js @@ -2,7 +2,7 @@ import React from 'react' import SortableList, { SortableItem } from 'react-easy-sort' import Icon from '@mdi/react' -import { mdiDrag, mdiEyeOutline, mdiEyeOff, mdiSquareOpacity } from '@mdi/js' +import { mdiDrag, mdiEyeOutline, mdiEyeOff, mdiSquareOpacity, mdiTerrain } from '@mdi/js' import { useServices, useList } from '../hooks' import Range from './Range' import { Tooltip } from 'react-tooltip' @@ -41,23 +41,21 @@ const Opacity = props => { /** * */ -const Layer = props => ( - - - {/* react-easy-sort kills list style => add necessary margins. */} -
-
-
- - {props.name} - { + + const icons = props.contentType?.includes('terrain') + ? + : <> + ( onClick={props.onToggleVisible} className='tt-tile-preset-visibility' /> + + + return ( + + {/* react-easy-sort kills list style => add necessary margins. */} +
+
+
+ + {props.name} + { icons } @@ -78,7 +90,8 @@ const Layer = props => (
-) + ) +} /** @@ -118,16 +131,17 @@ const LayerList = props => { tileLayerStore.toggleVisible(props.preset, id) } - const layers = list.entries.map(entry => - - ) + const layers = list.entries + .map(entry => + + ) // There seems to be no way to have decent CSS on list container. // We workaround this by applying ad-hoc styles. @@ -148,8 +162,11 @@ const LayerList = props => { /** * */ -const TilePresetProperties = props => ( +const TilePresetProperties = props => { + console.dir(props) + return ( -) + ) +} export default TilePresetProperties diff --git a/src/renderer/components/properties/TileServiceProperties.js b/src/renderer/components/properties/TileServiceProperties.js index 325ecc60..5ff30668 100644 --- a/src/renderer/components/properties/TileServiceProperties.js +++ b/src/renderer/components/properties/TileServiceProperties.js @@ -17,6 +17,7 @@ import { useList, useServices } from '../hooks' import * as TileService from '../../store/tileServiceAdapters' import './TileServiceProperties.css' import { Tooltip } from 'react-tooltip' +import Checkbox from './Checkbox' /** @@ -55,7 +56,7 @@ const fuseOptions = { * */ const TileServiceProperties = props => { - const { tileLayerStore, sessionStore } = useServices() + const { tileLayerStore, sessionStore, store } = useServices() const [key, service] = (Object.entries(props.features))[0] const [url, setUrl] = React.useState({ dirty: false, value: service.url || '' }) const [entries, setEntries] = React.useState([]) @@ -129,13 +130,27 @@ const TileServiceProperties = props => { } const handleZoomChange = ({ maxZoom }) => { - tileLayerStore.updateService(key, { ...service, ...{ capabilities: { maxZoom } } }) + const updatedService = { ...service } + updatedService.capabilities.maxZoom = maxZoom + tileLayerStore.updateService(key, updatedService /* { ...service, ...{ capabilities: { maxZoom } } } */) } const handleEntryChange = async id => tileLayerStore.toggleActiveLayer(key, id) const handleEntryClick = id => dispatch({ type: 'select', id }) const handleFilterChange = ({ target }) => setFilter(target.value) + const handleRGBTerrain = async ({ target }) => { + const updatedService = { ...service } + updatedService.capabilities.contentType = target.checked ? 'terrain/mapbox-rgb' : undefined + tileLayerStore.updateService(key, updatedService) + + if (target.checked) { + store.addTag(key, 'TERRAIN') + } else { + store.removeTag(key, 'TERRAIN') + } + } + const layerList = ['WMS', 'WMTS'].includes(service.type) ?
{ @@ -161,6 +176,10 @@ const TileServiceProperties = props => { ? : null + const terrainSelector = (service.type === 'XYZ') + ? + : null + return ( @@ -171,6 +190,7 @@ const TileServiceProperties = props => { { layerList }
{ zoomSliders } + { terrainSelector }
) diff --git a/src/renderer/model/OSDDriver.js b/src/renderer/model/OSDDriver.js index d5b8bc40..644a0f64 100644 --- a/src/renderer/model/OSDDriver.js +++ b/src/renderer/model/OSDDriver.js @@ -25,6 +25,13 @@ const formats = { UTM: ([lng, lat]) => new LatLon(lat, lng).toUtm().toString() } +const elevation = rgb => { + if (!rgb) return null + const value = -10000 + (((rgb[0] << 16) + (rgb[1] << 8) + rgb[2]) * 0.1) + if (value === -10000) return null + return value +} + export const OSDDriver = function (projectUUID, emitter, preferencesStore, projectStore, store) { this.projectUUID = projectUUID this.emitter = emitter @@ -51,13 +58,22 @@ export const OSDDriver = function (projectUUID, emitter, preferencesStore, proje }) } -OSDDriver.prototype.pointermove = function ({ coordinate }) { +OSDDriver.prototype.pointermove = function ({ coordinate, map, pixel }) { this.lastCoordinate = coordinate if (!this.coordinatesFormat) return const lonLat = toLonLat(coordinate) const message = formats[this.coordinatesFormat](lonLat) this.emitter.emit('osd', { message, cell: 'C2' }) + + const candids = map?.getLayerGroup().getLayersArray() + const terrainLayer = candids.find(l => l.get('contentType') === 'terrain/mapbox-rgb') + if (!terrainLayer) return + const data = terrainLayer.getData(pixel) + if (!data) return + const value = elevation(data) + const elevationMessage = value ? `${value.toFixed(1)}m` : '' + this.emitter.emit('osd', { message: elevationMessage, cell: 'C3' }) } OSDDriver.prototype.updateDateTime = function () { diff --git a/src/renderer/store/TileLayerStore.js b/src/renderer/store/TileLayerStore.js index 0762bc19..3cd94f4b 100644 --- a/src/renderer/store/TileLayerStore.js +++ b/src/renderer/store/TileLayerStore.js @@ -1,5 +1,5 @@ import * as R from 'ramda' -import { Tile as TileLayer } from 'ol/layer' +import WebGLTileLayer from 'ol/layer/WebGLTile.js' import Collection from 'ol/Collection' import LayerGroup from 'ol/layer/Group' import * as ID from '../ids' @@ -21,7 +21,8 @@ const fetchCapabilities = async service => { } catch (err) { return TileService.adapters.XYZ({ url: service.url, - maxZoom: service.capabilities?.maxZoom || 24 + maxZoom: service.capabilities?.maxZoom || 24, + contentType: service.capabilities?.contentType }) } } @@ -40,7 +41,17 @@ TileLayerStore.tileLayer = function (services) { const { type, capabilities } = services[ID.tileServiceId(id)] const adapter = TileService.adapters[type](capabilities) const source = adapter.source(ID.containedId(id)) - return new TileLayer({ source, id, opacity, visible, zIndex: 0 - index }) + const layerProps = { + source, + id, + opacity: capabilities?.contentType?.includes('terrain') ? 0 : opacity, + visible: capabilities?.contentType?.includes('terrain') ? true : visible, + zIndex: 0 - index, + contentType: capabilities?.contentType + } + + const layer = new WebGLTileLayer(layerProps) + return layer } } @@ -112,7 +123,6 @@ TileLayerStore.prototype.updateService = async function (key, service) { const name = service.name || title return { ...service, type, name, capabilities } } - const newValue = await capabilities(service) this.store.update([key], [newValue], [service]) } @@ -216,15 +226,26 @@ TileLayerStore.prototype.updatePreset = async function () { : adapters[key].layerName(ID.containedId(id)) } + const contentType = id => { + // eslint-disable-next-line no-unused-vars + const [key, service] = findService(ID.tileServiceId(id)) + return ['XYZ'].includes(service.type) + ? service.capabilities.contentType + : undefined + } + const layer = id => ({ id, opacity: 1.0, visible: false }) const additions = activeLayerIds.filter(x => !currentLayers.includes(x)).map(layer) // Propagate name changes from service to preset (only for OSM, XYZ.) - // const propagateName = layer => ({ ...layer, name: layerName(layer.id) }) + // Propagate content type (either undefined or terrain/mapbox-rgb) + const propagateContentType = layer => ({ ...layer, contentType: contentType(layer.id) }) + const preset = (currentPreset.concat(additions)) .filter(layer => !removals.includes(layer.id)) .map(propagateName) + .map(propagateContentType) this.store.update([ID.tilePresetId()], [preset]) } @@ -237,11 +258,16 @@ TileLayerStore.prototype.updateLayers = async function (preset) { const currentLayers = this.layerCollection.getArray() const findLayer = id => currentLayers.find(layer => layer.get('id') === id) const services = Object.fromEntries(await this.store.tuples(ID.TILE_SERVICE_SCOPE)) + // console.dir(preset) const updateLayer = (layer, properties, index) => { + /* console.dir(layer) */ + console.dir(properties) + layer.setOpacity(properties.opacity) layer.setVisible(properties.visible) layer.setZIndex(0 - index) + layer.set('contentType', properties.contentType) return layer } diff --git a/src/renderer/store/documents/tile-service.js b/src/renderer/store/documents/tile-service.js index eff280f0..9634663a 100644 --- a/src/renderer/store/documents/tile-service.js +++ b/src/renderer/store/documents/tile-service.js @@ -8,7 +8,7 @@ export default async function (id) { const document = { id, scope: ID.TILE_SERVICE, - text: service.name, + text: service?.name || '', tags: [...(tags || []), service.type] } diff --git a/src/renderer/store/options/tile-service.js b/src/renderer/store/options/tile-service.js index 123e2c04..aea07917 100644 --- a/src/renderer/store/options/tile-service.js +++ b/src/renderer/store/options/tile-service.js @@ -11,9 +11,12 @@ export default async function (id) { scope: service.type, tags: [ `SCOPE:${service.type}:NONE`, - ...((tags || [])).map(label => `USER:${label}:NONE`), + (tags || []).some(t => t === 'TERRAIN') ? 'SYSTEM:TERRAIN::mdiTerrain' : null, + ...((tags || [])) + .filter(Boolean) + .filter(t => t !== 'TERRAIN').map(label => `USER:${label}:NONE`), 'PLUS' - ].join(' '), + ].filter(Boolean).join(' '), capabilities: 'TAG|RENAME' } From bd0ea00d350db107b008c6120f5efdfd79aacc13 Mon Sep 17 00:00:00 2001 From: Thomas Halwax Date: Mon, 11 Dec 2023 12:34:20 +0100 Subject: [PATCH 02/73] fixed invalid visibility/opacity after update --- src/renderer/store/TileLayerStore.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/renderer/store/TileLayerStore.js b/src/renderer/store/TileLayerStore.js index 3cd94f4b..3aa99be2 100644 --- a/src/renderer/store/TileLayerStore.js +++ b/src/renderer/store/TileLayerStore.js @@ -258,14 +258,10 @@ TileLayerStore.prototype.updateLayers = async function (preset) { const currentLayers = this.layerCollection.getArray() const findLayer = id => currentLayers.find(layer => layer.get('id') === id) const services = Object.fromEntries(await this.store.tuples(ID.TILE_SERVICE_SCOPE)) - // console.dir(preset) - + const updateLayer = (layer, properties, index) => { - /* console.dir(layer) */ - console.dir(properties) - - layer.setOpacity(properties.opacity) - layer.setVisible(properties.visible) + layer.setOpacity(properties.contentType ? 0 : properties.opacity) + layer.setVisible(properties.contentType ? 1 : properties.visible) layer.setZIndex(0 - index) layer.set('contentType', properties.contentType) return layer From 2dbce88fbcf8f36c82414235b5a5b10b1712df5c Mon Sep 17 00:00:00 2001 From: Thomas Halwax Date: Mon, 11 Dec 2023 12:34:29 +0100 Subject: [PATCH 03/73] comment --- src/renderer/components/properties/TilePresetProperties.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderer/components/properties/TilePresetProperties.js b/src/renderer/components/properties/TilePresetProperties.js index b14ca268..43e7fe5b 100644 --- a/src/renderer/components/properties/TilePresetProperties.js +++ b/src/renderer/components/properties/TilePresetProperties.js @@ -43,6 +43,7 @@ const Opacity = props => { */ const Layer = props => { + // terrain data layers must not be invisible, thus we hide controls const icons = props.contentType?.includes('terrain') ? { +
Date: Mon, 11 Dec 2023 12:34:44 +0100 Subject: [PATCH 04/73] allow stacked terrain layers --- src/renderer/model/OSDDriver.js | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/renderer/model/OSDDriver.js b/src/renderer/model/OSDDriver.js index 644a0f64..e18e7d77 100644 --- a/src/renderer/model/OSDDriver.js +++ b/src/renderer/model/OSDDriver.js @@ -67,12 +67,23 @@ OSDDriver.prototype.pointermove = function ({ coordinate, map, pixel }) { this.emitter.emit('osd', { message, cell: 'C2' }) const candids = map?.getLayerGroup().getLayersArray() - const terrainLayer = candids.find(l => l.get('contentType') === 'terrain/mapbox-rgb') - if (!terrainLayer) return - const data = terrainLayer.getData(pixel) - if (!data) return - const value = elevation(data) - const elevationMessage = value ? `${value.toFixed(1)}m` : '' + const terrainLayers = candids.filter(l => l.get('contentType') === 'terrain/mapbox-rgb') + if (terrainLayers.length === 0) { + this.emitter.emit('osd', { message: '', cell: 'C3' }) + return + } + + const data = terrainLayers + .map(l => l.getData(pixel)) + .map(d => elevation(d)) + .filter(Boolean) + + if (data.length === 0) { + this.emitter.emit('osd', { message: '', cell: 'C3' }) + return + } + + const elevationMessage = `${data[0].toFixed(1)}m` this.emitter.emit('osd', { message: elevationMessage, cell: 'C3' }) } From 06b3d670ae2e9a28be757048aee6120d93aa764f Mon Sep 17 00:00:00 2001 From: Thomas Halwax Date: Tue, 12 Dec 2023 10:36:07 +0100 Subject: [PATCH 05/73] tags are not taggable --- src/renderer/ids.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/ids.js b/src/renderer/ids.js index fc0c7ec0..0f817770 100644 --- a/src/renderer/ids.js +++ b/src/renderer/ids.js @@ -97,7 +97,7 @@ export const isMeasureId = isId(MEASURE_SCOPE) export const isStylableId = R.anyPass([isLayerId, isFeatureId]) export const isDeletableId = id => !isSymbolId(id) -export const isTaggableId = id => !isViewId(id) +export const isTaggableId = id => (!isViewId(id) && !isTagsId(id)) export const isAssociatedId = R.anyPass([isHiddenId, isLockedId, isDefaultId, isTagsId]) export const layerUUID = R.cond([ From 6fba843f9dc187fa3fcc912421de3ca6bd8dc092 Mon Sep 17 00:00:00 2001 From: Thomas Halwax Date: Tue, 12 Dec 2023 12:00:23 +0100 Subject: [PATCH 06/73] return null for non-existing service --- src/renderer/store/documents/tile-service.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/store/documents/tile-service.js b/src/renderer/store/documents/tile-service.js index 9634663a..d473b7c1 100644 --- a/src/renderer/store/documents/tile-service.js +++ b/src/renderer/store/documents/tile-service.js @@ -5,10 +5,12 @@ export default async function (id) { const keys = [R.identity, ID.tagsId] const [service, tags] = await this.store.collect(id, keys) + if (!service) return null + const document = { id, scope: ID.TILE_SERVICE, - text: service?.name || '', + text: service.name || '', tags: [...(tags || []), service.type] } From 8c2f38f245f62ce692ff49ded3643bb44d00d521 Mon Sep 17 00:00:00 2001 From: Thomas Halwax Date: Tue, 12 Dec 2023 12:08:28 +0100 Subject: [PATCH 07/73] moved to an async implementation --- src/renderer/model/OSDDriver.js | 35 ++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/renderer/model/OSDDriver.js b/src/renderer/model/OSDDriver.js index e18e7d77..d7a08ff2 100644 --- a/src/renderer/model/OSDDriver.js +++ b/src/renderer/model/OSDDriver.js @@ -66,25 +66,28 @@ OSDDriver.prototype.pointermove = function ({ coordinate, map, pixel }) { const message = formats[this.coordinatesFormat](lonLat) this.emitter.emit('osd', { message, cell: 'C2' }) - const candids = map?.getLayerGroup().getLayersArray() - const terrainLayers = candids.filter(l => l.get('contentType') === 'terrain/mapbox-rgb') - if (terrainLayers.length === 0) { - this.emitter.emit('osd', { message: '', cell: 'C3' }) - return + const getElevation = async () => { + const candids = map?.getLayerGroup().getLayersArray() + const terrainLayers = candids.filter(l => l.get('contentType') === 'terrain/mapbox-rgb') + if (terrainLayers.length === 0) { + return '' + } + + const data = terrainLayers + .map(l => l.getData(pixel)) + .map(d => elevation(d)) + .filter(Boolean) + + if (data.length === 0) { + return '' + } + + const elevationMessage = `${data[0].toFixed(1)}m` + return elevationMessage } - const data = terrainLayers - .map(l => l.getData(pixel)) - .map(d => elevation(d)) - .filter(Boolean) + getElevation().then(message => this.emitter.emit('osd', { message, cell: 'C3' })) - if (data.length === 0) { - this.emitter.emit('osd', { message: '', cell: 'C3' }) - return - } - - const elevationMessage = `${data[0].toFixed(1)}m` - this.emitter.emit('osd', { message: elevationMessage, cell: 'C3' }) } OSDDriver.prototype.updateDateTime = function () { From 003b167310c28e933c1733285890bb90f1cf5b53 Mon Sep 17 00:00:00 2001 From: Thomas Halwax Date: Tue, 12 Dec 2023 12:11:56 +0100 Subject: [PATCH 08/73] removed debug output --- src/renderer/components/properties/TilePresetProperties.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/components/properties/TilePresetProperties.js b/src/renderer/components/properties/TilePresetProperties.js index 43e7fe5b..c4b99ce5 100644 --- a/src/renderer/components/properties/TilePresetProperties.js +++ b/src/renderer/components/properties/TilePresetProperties.js @@ -165,7 +165,6 @@ const LayerList = props => { * */ const TilePresetProperties = props => { - console.dir(props) return ( ) From 4855efca90a69378eb53abe0312eee4a071d09f8 Mon Sep 17 00:00:00 2001 From: Thomas Halwax Date: Thu, 14 Dec 2023 18:08:58 +0100 Subject: [PATCH 09/73] more detailed source map --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 8091e387..3a9898e1 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -125,7 +125,7 @@ const devServer = env => { const devtool = env => { if (env.production) return ({}) // no source maps for production return ({ - devtool: 'cheap-source-map' + devtool: 'source-map' }) } From da7dde957d0d9edc197ce8aa0365e6c15457b99a Mon Sep 17 00:00:00 2001 From: dehmer Date: Sat, 22 Jun 2024 20:22:53 +0200 Subject: [PATCH 10/73] replaced most.js with homegrown signal module. --- package-lock.json | 85 +----- package.json | 5 +- src/renderer/ol/interaction/modify/events.js | 89 ------ src/renderer/ol/interaction/modify/index.js | 259 +++++++----------- src/renderer/ol/interaction/modify/states.js | 8 +- src/renderer/ol/interaction/modify/writers.js | 4 +- src/renderer/store/FeatureStore.js | 20 +- 7 files changed, 132 insertions(+), 338 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b1667cc..ebb591f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.0", - "@most/scheduler": "^1.3.0", + "@syncpoint/signal": "github:syncpoint/signal#vite", "@syncpoint/signs": "^1.0.1", "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", @@ -28,7 +28,6 @@ "levelup": "^5.0.1", "luxon": "^3.1.0", "minisearch": "^6.0.1", - "most-subject": "^6.0.0", "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", @@ -2718,64 +2717,6 @@ "prop-types": "^15.7.2" } }, - "node_modules/@most/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@most/core/-/core-0.14.0.tgz", - "integrity": "sha512-Ns8ckDZ7iGKfUgM9Ljp+fR2gnq71j5cAGXTWEDCSZH2KaLRfxs7pp0/oxljNN3aIvcQQcK1MD1Jf9mGo+aUiUA==", - "dependencies": { - "@most/disposable": "^0.13.1", - "@most/prelude": "^1.6.4", - "@most/scheduler": "^0.13.1", - "@most/types": "^0.11.1" - } - }, - "node_modules/@most/core/node_modules/@most/scheduler": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@most/scheduler/-/scheduler-0.13.1.tgz", - "integrity": "sha512-t14TLvEfUYPSLJQSLT/V5bQYxvKdLib8KFrz0hniOHZRoqyBgt6zhHKP1zywrEYQd9+sdhS5SZDnX9wAJ+xJxg==", - "dependencies": { - "@most/prelude": "^1.6.4", - "@most/types": "^0.11.1" - } - }, - "node_modules/@most/core/node_modules/@most/types": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@most/types/-/types-0.11.1.tgz", - "integrity": "sha512-acP+xy7F4W50GAbpOmfdNgATPwCfMUSLmWEhUb4MHli9k0qAtN828LTVPgK3AsTBh/Fkx0TulbgW+H2t06BsKA==" - }, - "node_modules/@most/disposable": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@most/disposable/-/disposable-0.13.1.tgz", - "integrity": "sha512-2Z89nEjdEJ6RvCzwyWZK6xv2avt2gY2NSkceemOQ869ggGDB2ZFA/ZMuyZag2KVoCyjb9zMOhfOgQw1gD/m9QA==", - "dependencies": { - "@most/prelude": "^1.6.4", - "@most/types": "^0.11.1" - } - }, - "node_modules/@most/disposable/node_modules/@most/types": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@most/types/-/types-0.11.1.tgz", - "integrity": "sha512-acP+xy7F4W50GAbpOmfdNgATPwCfMUSLmWEhUb4MHli9k0qAtN828LTVPgK3AsTBh/Fkx0TulbgW+H2t06BsKA==" - }, - "node_modules/@most/prelude": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@most/prelude/-/prelude-1.8.0.tgz", - "integrity": "sha512-t1CcURpZzfmBA6fEWwqmCqeNzWAj1w2WqEmCk/2yXMe/p8Ut000wFmVKMy8A1Rl9VVxZEZ5nBHd/pU0dR4bv/w==" - }, - "node_modules/@most/scheduler": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@most/scheduler/-/scheduler-1.3.0.tgz", - "integrity": "sha512-bbJGyhbZxNqlkVjP8+YT9wIVMvYnpzWOzV8jZueqlTH2PJWewH2f54YziZn7/wWa6AJdN03H1vb8Tbi9GcA/cw==", - "dependencies": { - "@most/prelude": "^1.8.0", - "@most/types": "^1.1.0" - } - }, - "node_modules/@most/types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@most/types/-/types-1.1.0.tgz", - "integrity": "sha512-v2trqAWu1jqP4Yd/CyI1O6mAeJyygK1uJOrFRpNPkPZIaYw4khA4EQe4WzcyOFKuXdiP8qAqaxGtXXJJ2LZdXg==" - }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -2978,6 +2919,10 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@syncpoint/signal": { + "version": "1.2.0", + "resolved": "git+ssh://git@github.com/syncpoint/signal.git#08cfb29c6bfd715c34fe93744437932db62b20da" + }, "node_modules/@syncpoint/signs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@syncpoint/signs/-/signs-1.0.1.tgz", @@ -12009,26 +11954,6 @@ "node": ">=10" } }, - "node_modules/most-subject": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/most-subject/-/most-subject-6.0.0.tgz", - "integrity": "sha512-Fg55kwepLJ4l6tI/iOWaKDV0pNpsv0KMprr4skdZlKu292bN0xkT8zE12CybkVhivWlQP3QrweSk8eOKoJUMmA==", - "dependencies": { - "@most/core": "0.14.0", - "@most/prelude": "1.6.4", - "@most/types": "0.11.1" - } - }, - "node_modules/most-subject/node_modules/@most/prelude": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/@most/prelude/-/prelude-1.6.4.tgz", - "integrity": "sha512-RsT1xRIEc+rCCTZPL3v/tAC+dX1qt1q00ZofEtCJEMEsVg7zT+WLXiVQdhcRqqh4baQTYmDPArXrQWyd7GkqAA==" - }, - "node_modules/most-subject/node_modules/@most/types": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@most/types/-/types-0.11.1.tgz", - "integrity": "sha512-acP+xy7F4W50GAbpOmfdNgATPwCfMUSLmWEhUb4MHli9k0qAtN828LTVPgK3AsTBh/Fkx0TulbgW+H2t06BsKA==" - }, "node_modules/mousetrap": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", diff --git a/package.json b/package.json index a66227ee..ebf64dc8 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "Open Source Command and Control Information System (C2IS)", "main": "dist/main.js", "scripts": { - "start": "electron . --cold", + "start": "electron . --cold --trace-warnings", "hot": "webpack serve", "webpack": "webpack", "webpack:production": "webpack --mode=production", @@ -64,7 +64,7 @@ "dependencies": { "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.0", - "@most/scheduler": "^1.3.0", + "@syncpoint/signal": "github:syncpoint/signal#vite", "@syncpoint/signs": "^1.0.1", "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", @@ -80,7 +80,6 @@ "levelup": "^5.0.1", "luxon": "^3.1.0", "minisearch": "^6.0.1", - "most-subject": "^6.0.0", "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", diff --git a/src/renderer/ol/interaction/modify/events.js b/src/renderer/ol/interaction/modify/events.js index 4ca94b0d..e1782fbc 100644 --- a/src/renderer/ol/interaction/modify/events.js +++ b/src/renderer/ol/interaction/modify/events.js @@ -1,7 +1,5 @@ import * as R from 'ramda' import Event from 'ol/events/Event' -import { currentTime } from '@most/scheduler' -import { map } from '@most/core' import { Coordinate } from './coordinate' /** @@ -126,90 +124,3 @@ export const pointer = (options, rbush, event) => { return pointer } - -/** - * fromListeners :: [string] -> EventTarget -> Stream - */ -export const fromListeners = (types, target) => ({ - run: (sink, scheduler) => { - const push = event => sink.event(currentTime(scheduler), event) - types.forEach(type => target.addEventListener(type, push)) - - return { - dispose: () => types.forEach(type => target.removeEventListener(type, push)) - } - } -}) - -export class Pipe { - constructor (sink) { this.sink = sink } - end (time) { this.sink.end(time) } - error (time, err) { this.sink.end(time, err) } -} - -class Op { - constructor (sink, stream) { - this.sink = sink - this.stream = stream - } - - run (sink, scheduler) { - return this.stream.run(this.sink(sink), scheduler) - } -} - -class Replace { - constructor (stream) { - this.stream = stream - } - - run (sink, scheduler) { - return this.stream.run(new ReplaceSink(sink, scheduler), scheduler) - } -} - -class ReplaceSink extends Pipe { - constructor (sink, scheduler) { - super(sink) - this.scheduler = scheduler - this.disposable = { - dispose: () => {} - } - } - - event (time, stream) { - this.disposable.dispose() - this.disposable = stream.run(this.sink, this.scheduler) - } -} - -/** - * Flatten stream of arrays with depth = 1. - */ -class Flat { - constructor (stream) { - this.stream = stream - } - - run (sink, scheduler) { - this.stream.run(new FlatSink(sink), scheduler) - } -} - -class FlatSink extends Pipe { - event (time, xs) { - if (!Array.isArray(xs)) this.sink.event(time, xs) - else xs.forEach(x => this.sink.event(time, x)) - } -} - -export const flat = stream => new Flat(stream) -export const flatN = n => Array(n).fill(flat).reduce((f, g) => R.compose(g, f)) - -/** - * replace, aka SwitchAll (RxJS) - */ -export const replace = stream => new Replace(stream) -export const orElse = value => map(that => that || value) -export const op = sink => stream => new Op(sink, stream) -export const pipe = ops => stream => ops.reduce((acc, op) => op(acc), stream) diff --git a/src/renderer/ol/interaction/modify/index.js b/src/renderer/ol/interaction/modify/index.js index 13ab3d18..1cb2d6e7 100644 --- a/src/renderer/ol/interaction/modify/index.js +++ b/src/renderer/ol/interaction/modify/index.js @@ -5,57 +5,69 @@ import VectorLayer from 'ol/layer/Vector' import VectorSource from 'ol/source/Vector' import Point from 'ol/geom/Point' import * as style from 'ol/style' -import * as Subject from 'most-subject' -import * as M from '@most/core' -import { runEffects } from '@most/core' -import { newDefaultScheduler, currentTime } from '@most/scheduler' -import { writeIndex } from './writers' -import { setCoordinates } from '../../../model/geometry' +import Signal from '@syncpoint/signal' import * as Events from './events' -import { ModifyEvent, pipe, fromListeners, replace, orElse, op, flat } from './events' import { selected } from './states' +import { ModifyEvent } from './events' +import { writeIndex } from './writers' /** * Sink for vertex feature overlay. * Accept coordinate events and update feature accordingly. */ -export class OverlaySink { - constructor (style, options) { +class OverlaySink { + constructor (style, options, coordinate) { const source = new VectorSource({ useSpatialIndex: false, wrapX: !!options.wrapX }) - this.style = style this.layer = new VectorLayer({ source, updateWhileAnimating: true, updateWhileInteracting: true }) + + this.dispose = Signal.on(coordinate => { + const feature = source.getFeatureById('feature:pointer') + if (coordinate && !feature) { + const pointer = new Feature(new Point(coordinate)) + pointer.setId('feature:pointer') + pointer.setStyle(style) + source.addFeature(pointer) + } else if (coordinate && feature) { + const geometry = feature.getGeometry() + geometry.setCoordinates(coordinate) + } else if (!coordinate && feature) { + source.removeFeature(feature) + } + }, coordinate) } - setMap (map) { this.layer.setMap(map) } - end (time) { console.log('[OverlaySink] end') } - error (time, err) { console.error('[OverlaySink] error', err) } - - event (time, coordinate) { - const source = this.layer.getSource() - const feature = source.getFeatureById('feature:pointer') - - if (coordinate && !feature) { - const pointer = new Feature(new Point(coordinate)) - pointer.setId('feature:pointer') - pointer.setStyle(this.style) - source.addFeature(pointer) - } else if (coordinate && feature) { - const geometry = feature.getGeometry() - geometry.setCoordinates(coordinate) - } else if (!coordinate && feature) { - source.removeFeature(feature) - } + setMap (map) { + if (!map) this.dispose() + this.layer.setMap(map) } } +/** + * rbush :: ol/Feature => Signal ol/structs/RBush + */ +const rbush = (() => { + let changeEvent + + return feature => { + if (changeEvent) changeEvent.dispose() + changeEvent = Signal.fromListeners(['change'], feature) + return R.compose( + Signal.startWith(() => writeIndex(feature)), + R.map(({ target }) => writeIndex(target)), + R.reject(({ target }) => target.internalChange()) + )(changeEvent) + } +})() + + /** * */ @@ -64,150 +76,79 @@ export class Modify extends Interaction { constructor (options) { super(options) - this.overlay = new OverlaySink(pointerStyles.DEFAULT, options) - - // Setup subject to receive map browser events: - const [sink, event$] = Subject.create() - this.next = event => Subject.event(currentTime(scheduler), event, sink) - - // Apply (rbush, event) to current state and update state accordingly. - const eventLoop = (state, [rbush, event]) => { - + // this.mapEvent :: Signal ol/MapBrowserEvent + // Receive map browser (mouse, keyboard) events. + this.mapEvent = Signal.of() + + // feature :: [ol/Feature] => ol/Feature + // Single feature or placeholder feature without geometry. + const feature = features => + features.length === 1 + ? features[0] + : new Feature() + + // isSymbol :: ol/Feature => Boolean + const isSymbol = feature => feature?.getGeometry()?.getType() === 'Point' + + // sourceEvent :: Signal ol/source/Vector.VectorSourceEvent + // Track feature add/remove events to update spatial index. + const sourceEvent = Signal.fromListeners(['addfeature', 'removefeature'], options.source) + + // spatialIndex :: Signal ol/structs/RBush + const spatialIndex = R.compose( + R.chain(rbush), + R.reject(isSymbol), + R.map(feature), + R.map(({ target }) => target.getFeatures()) + )(sourceEvent) + + // spatialEvent :: Signal [ol/structs/RBush, ol/MapBrowserEvent] + // Map browser event in context of current spatial index. + const spatialEvent = + Signal.lift((rbush, event) => [rbush, event], spatialIndex, this.mapEvent) + + // eventHandler :: Coordinate [Number, Number] => + // State -> [ol/structs/RBush, ol/MapBrowserEvent] -> [State, Coordinate] + const eventHandler = (state, [rbush, event]) => { // For empty index, reset to loaded state: - if (rbush.isEmpty()) return { seed: selected(true), value: Events.coordinate(null) } - + if (rbush.isEmpty()) return [selected(true), Events.coordinate(null)] const pointer = Events.pointer(options, rbush, event) const handler = state[event.type] - const [seed, value] = (handler && handler(pointer)) || [state, null] - return { seed, value } + return (handler && handler(pointer)) || [state, null] } - // Setup RBush stream from vector source add/remove - // feature and feature change events: - const rbush$ = Modify.rbush(options.source) - - const pipeline$ = pipe([ - - // combine :: (a -> b -> c) -> Stream a -> Stream b -> Stream c - // Apply a function to the most recent event from each Stream - // when a new event arrives on any Stream. - M.combine((rbush, event) => [rbush, event], rbush$), - M.loop(eventLoop, selected(true)), - M.filter(R.identity), - flat, - M.multicast - ])(event$) - - // Coordinate path. Pipe coordinate events to overlay. - const coordinate$ = pipe([ - M.filter(event => event.type === 'coordinate'), - M.map(({ coordinate }) => coordinate), - op(() => this.overlay) - ])(pipeline$) - - // Update path. Pipe update events to store (indirectly). - const update$ = pipe([ - M.filter(event => event instanceof ModifyEvent), - M.tap(event => this.dispatchEvent(event)) - ])(pipeline$) - - const scheduler = newDefaultScheduler() - runEffects(coordinate$, scheduler) - runEffects(update$, scheduler) + // stateLoop :: Signal { type: 'coordinate, ... } | ModifyEvent + const stateLoop = R.compose( + R.reject(R.isNil), + Signal.loop(eventHandler, selected(true)) + )(spatialEvent) + + // coordinate :: Coordinate [Number, Number] => Signal Coordinate + // Filter coordinates from state loop. + const coordinate = R.compose( + R.map(({ coordinate }) => coordinate), + R.filter(event => event.type === 'coordinate'), + )(stateLoop) + + // modifyEvent :: Signal + // Filter ModifyEvent to be dispatched from state loop. + const modifyEvent = R.compose( + Signal.filter(event => event instanceof ModifyEvent) + )(stateLoop) + + // Effects: Update overlay feature coordinate, dispatch modify event + this.overlay = new OverlaySink(pointerStyles.DEFAULT, options, coordinate) + Signal.link(event => this.dispatchEvent(event), modifyEvent) } handleEvent (event) { - this.next(event) + this.mapEvent(event) // Returning true will propagate event to next interaction, // unless stopPropagation() was called on event. return true } - /** - * rbush :: RBush a => ol/VectorSource -> Stream a - */ - static rbush (source) { - - const isSymbol = feature => - feature.getGeometry() && - feature.getGeometry().getType() === 'Point' - - const pipeline = pipe([ - M.map(({ target }) => target.getFeatures()), - - // Only one selected feature allowed: - M.map(features => features.length === 1 ? features[0] : null), - - // Replace null with dummy feature without geometry: - orElse(new Feature()), - - // Exclude 1-point symbols from modify: - M.filter(feature => R.not(isSymbol(feature))), - - M.skipRepeats, - - // map :: RBush a => ol.Feature -> Stream a - // Higher-order stream of RBush events. - M.map(Modify.featureProxy), - - // Switch to new RBush stream as it arrives, - // ending the previous stream. RBush events - // are piped/flattened to output stream. - replace - ]) - - const source$ = fromListeners(['addfeature', 'removefeature'], source) - return pipeline(source$) - } - - /** - * createProxy :: RBush a => ol/Feature -> Stream a - * Feature proxy with - * @property coordinates - writable, geometry coordinates - * @property commit - emit change event for feature - */ - static featureProxy (feature) { - // feature change event: false -> external, true -> internal - let internalChange = false - - const handlers = { - set (target, property, value) { - // coordinates setter on feature proxy: - if (property === 'coordinates') { - internalChange = true - setCoordinates(target.getGeometry(), value) - internalChange = false - return true - } else { - return Reflect.set(target, property, value) - } - } - } - - const proxy = new Proxy(feature, handlers) - - // Simulate external change by emitting change event explicitly. - proxy.commit = () => { - // Must not happen inside this stack frame: - const event = { type: 'change', target: feature } - const dispatch = () => feature.dispatchEvent(event) - setTimeout(dispatch, 0) - } - - // Create new spatial index for each external change event: - const pipeline = pipe([ - M.filter(() => !internalChange), - M.map(() => writeIndex(proxy)), - - // Initial spatial index: - M.startWith(writeIndex(proxy)) - ]) - - const change$ = fromListeners(['change'], feature) - return pipeline(change$) - } - setMap (map) { super.setMap(map) this.overlay.setMap(map) diff --git a/src/renderer/ol/interaction/modify/states.js b/src/renderer/ol/interaction/modify/states.js index f11e3e1f..d5150f86 100644 --- a/src/renderer/ol/interaction/modify/states.js +++ b/src/renderer/ol/interaction/modify/states.js @@ -72,7 +72,7 @@ export const selected = (handleClick = false) => ({ const { segment, index } = pick const { feature } = segment - feature.coordinates = removeVertex(segment, index) + feature.updateCoordinates(removeVertex(segment, index)) feature.commit() return [selected(), [ @@ -92,7 +92,7 @@ const drag = (feature, update) => ({ const [coordinates, coordinate] = update(pointer.coordinate, pointer.condition) // Side-effect: Update feature coordinates and thus geometry. - feature.coordinates = coordinates + feature.updateCoordinates(coordinates) return [drag(feature, update), Events.coordinate(coordinate)] }, @@ -119,7 +119,7 @@ const insert = pick => { const coordinate = pointer.coordinate const feature = segment.feature const [coordinates, update] = insertVertex(segment, coordinate) - feature.coordinates = coordinates + feature.updateCoordinates(coordinates) return [drag(feature, update), [Events.coordinate(coordinate)]] } }, @@ -138,7 +138,7 @@ const remove = pick => { return { pointerup: () => { const feature = segment.feature - feature.coordinates = removeVertex(segment, index) + feature.updateCoordinates(removeVertex(segment, index)) feature.commit() // Remain in REMOVE state and wait for next click event: diff --git a/src/renderer/ol/interaction/modify/writers.js b/src/renderer/ol/interaction/modify/writers.js index 28935709..b836847d 100644 --- a/src/renderer/ol/interaction/modify/writers.js +++ b/src/renderer/ol/interaction/modify/writers.js @@ -6,7 +6,7 @@ import { geometryType } from '../../../model/geometry' import { Hooks } from './hooks' /** - * signature :: ol.Feature -> String + * signature :: ol/Feature -> String * Geometry type plus optional layout from feature descriptors. */ const signature = feature => { @@ -19,7 +19,7 @@ const signature = feature => { } /** - * writeIndex :: ol.Feature => ol.structs.RBush + * writeIndex :: ol/Feature => ol/structs/RBush * Create new spatial index (R-Bush) from feature. */ export const writeIndex = feature => { diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index 8b1609b7..c4d41be1 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -3,9 +3,10 @@ import * as R from 'ramda' import util from 'util' import GeoJSON from 'ol/format/GeoJSON' import * as Extent from 'ol/extent' +import Signal from '@syncpoint/signal' import Emitter from '../../shared/emitter' import { debounce, batch } from '../../shared/debounce' -import { geometryType } from '../model/geometry' +import { geometryType, setCoordinates } from '../model/geometry' import * as ID from '../ids' import { reduce, rules } from '../ol/style/rules' import crosshair from '../ol/style/crosshair' @@ -266,6 +267,23 @@ FeatureStore.prototype.wrapFeature = function (feature) { R.when(Boolean, set('layerStyle'))(this.styleProps['style+' + layerId]) R.when(Boolean, set('featureStyle'))(this.styleProps['style+' + featureId]) + // Use dedicated function to update feature coordinates from within + // modify interaction. Such internal changes must not trigger ModifyEvent. + + feature.internalChange = Signal.of(false) + + feature.updateCoordinates = coordinates => { + feature.internalChange(true) + setCoordinates(feature.getGeometry(), coordinates) + feature.internalChange(false) + } + + feature.commit = () => { + // Event must be deferred so that event handler has a chance + // to update to a new state (drag -> selected). + setTimeout(() => feature.dispatchEvent({ type: 'change', target: feature })) + } + feature.setStyle((feature, resolution) => { const { geometry: definingGeometry, ...properties } = feature.getProperties() state = reduce(state, { From 1cbb3eca6906955594f060a568be6f51a2ab2192 Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 24 Jun 2024 10:12:23 +0200 Subject: [PATCH 11/73] playing with signals. --- src/renderer/store/FeatureStore.js | 239 +++++++++++++++-------------- 1 file changed, 122 insertions(+), 117 deletions(-) diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index c4d41be1..6230db36 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -5,7 +5,6 @@ import GeoJSON from 'ol/format/GeoJSON' import * as Extent from 'ol/extent' import Signal from '@syncpoint/signal' import Emitter from '../../shared/emitter' -import { debounce, batch } from '../../shared/debounce' import { geometryType, setCoordinates } from '../model/geometry' import * as ID from '../ids' import { reduce, rules } from '../ol/style/rules' @@ -14,6 +13,21 @@ import { stylist as measurementStyler } from '../ol/interaction/measure/style' import * as TS from '../ol/ts' import * as Math from '../../shared/Math' +Signal.split = (conditions, signal) => { + const outputs = conditions.map(() => Signal.of()) + signal.on(value => { + const match = condition => condition(value) + outputs[conditions.findIndex(match)]?.(value) + }) + return outputs +} + +Signal.flatten = signal => { + const output = Signal.of() + signal.on(v => (Array.isArray(v) ? v : [v]).forEach(output)) + return output +} + const format = new GeoJSON({ dataProjection: 'EPSG:3857', featureProjection: 'EPSG:3857' @@ -32,6 +46,20 @@ export const writeGeometryObject = geometry => format.writeGeometryObject(geomet export const writeFeatureCollection = features => format.writeFeaturesObject(features) export const writeFeatureObject = feature => format.writeFeatureObject(feature) +const isCandidateId = id => ID.isFeatureId(id) || ID.isMarkerId(id) || ID.isMeasureId(id) + +const isGeometry = value => { + if (!value) return false + else if (typeof value !== 'object') return false + else { + if (!value.type) return false + else if (!value.coordinates && !value.geometries) return false + return true + } +} + +const apply = options => feature => feature && feature.apply(options, true) + /** * @@ -42,8 +70,99 @@ export function FeatureStore (store, selection) { this.features = {} this.styleProps = {} - const debouncedHandler = batch(debounce(32), this.batch.bind(this)) - store.on('batch', ({ operations }) => debouncedHandler(operations)) + // FIXME: Signal should support on/off + store.addEventListener = (type, handler) => store.on(type, handler) + store.removeEventListener = (type, handler) => store.off(type, handler) + selection.addEventListener = (type, handler) => selection.on(type, handler) + selection.removeEventListener = (type, handler) => selection.off(type, handler) + + const $operations = R.compose( + Signal.flatten, + R.map(R.prop('operations')) + )(Signal.fromListeners(['batch'], store)) + + const [ + $globalStyle, + $featureStyle, + $layerStyle, + $feature + ] = Signal.split([ + R.propEq(ID.defaultStyleId, 'key'), + R.compose(ID.isFeatureStyleId, R.prop('key')), + R.compose(ID.isLayerStyleId, R.prop('key')), + R.compose(isCandidateId, R.prop('key')) + ], $operations) + + $globalStyle.on(({ value }) => Object.values(this.features).forEach(apply({ globalStyle: value }))) + $featureStyle.on(({ key, value }) => apply({ featureStyle: value })(this.features[ID.featureId(key)])) + + $featureStyle.on(console.log) + + $layerStyle.on(({ type, key, value }) => { + if (type === 'put') this.styleProps[key] = value + else delete this.styleProps[key] + const layerStyle = type === 'put' ? value : {} + const layerId = ID.layerId(key) + Object + .keys(this.features) + .filter(key => ID.layerId(key) === layerId) + .forEach(key => apply({ layerStyle })(this.features[key])) + }) + + const [ + $removal, + $update, + $addition + ] = Signal.split([ + R.propEq('del', 'type'), + ({ key }) => this.features[key], + R.T + ], $feature) + + const isValid = feature => feature?.type === 'Feature' && feature.geometry + + const trim = properties => { + const { geometry, ...rest } = properties + return geometry + ? properties + : rest + } + + $removal.on(({ key }) => { + const features = [this.features[key]] + delete this.features[key] + this.emit('removefeatures', ({ features })) + }) + + $addition.on(({ key, value }) => { + if (isValid) this.addFeatures([readFeature({ id: key, ...value })]) + }) + + $update.on(({ key, value }) => { + const properties = isGeometry(value) + ? { geometry: readGeometry(value) } + : trim(readFeature(value).getProperties()) + + const feature = this.features[key] + feature.setProperties({ ...feature.getProperties(), ...properties }) + }) + + const $selection = Signal.fromListeners(['selection'], selection) + const modes = ({ deselected }) => { + const selected = selection.selected() + const mode = selected.length > 1 ? 'multiselect' : 'singleselect' + return [ + ...deselected.map(key => [key, 'default']), + ...selected.map(key => [key, mode]) + ] + } + + const $mode = R.compose( + Signal.flatten, + R.map(modes) + )($selection) + + $mode.on(console.log) selection.on('selection', ({ deselected, selected }) => { deselected.forEach(key => { @@ -88,120 +207,6 @@ FeatureStore.prototype.loadFeatures = async function (scope) { } -/** - * - */ -FeatureStore.prototype.batch = function (operations) { - const features = Object.values(this.features) - const apply = obj => feature => feature.apply(obj, true) - - operations - .filter(({ key }) => key === ID.defaultStyleId) - .forEach(({ value }) => features.forEach(apply({ globalStyle: value }))) - - operations - .filter(({ key }) => ID.isFeatureStyleId(key)) - .forEach(({ key, value }) => apply({ featureStyle: value })(this.features[ID.featureId(key)])) - - // Apply new/updated layer styles: - // - operations - .filter(({ type }) => type === 'put') - .filter(({ key }) => ID.isLayerStyleId(key)) - .forEach(({ key, value }) => { - this.styleProps[key] = value - const layerId = ID.layerId(key) - Object - .keys(this.features) - .filter(key => ID.layerId(key) === layerId) - .forEach(key => apply({ layerStyle: value })(this.features[key])) - }) - - // Remove deleted layer styles: - // - operations - .filter(({ type }) => type === 'del') - .filter(({ key }) => ID.isLayerStyleId(key)) - .forEach(({ key }) => { - delete this.styleProps[key] - const layerId = ID.layerId(key) - Object - .keys(this.features) - .filter(key => ID.layerId(key) === layerId) - .forEach(key => apply({ layerStyle: {} })(this.features[key])) - }) - - const isCandidateId = id => ID.isFeatureId(id) || ID.isMarkerId(id) || ID.isMeasureId(id) - const candidates = operations.filter(({ key }) => isCandidateId(key)) - const [removals, other] = R.partition(({ type }) => type === 'del', candidates) - const [updates, additions] = R.partition(({ key }) => this.features[key], other) - this.handleRemovals(removals) - this.handleAdditions(additions) - this.handleUpdates(updates) -} - - -/** - * - */ -FeatureStore.prototype.handleAdditions = function (additions) { - if (additions.length === 0) return - const isValid = feature => feature?.type === 'Feature' && feature.geometry - const features = additions - .filter(({ value }) => isValid(value)) - .map(({ key, value }) => readFeature({ id: key, ...value })) - - this.addFeatures(features) -} - - -/** - * - */ -FeatureStore.prototype.handleRemovals = function (removals) { - if (removals.length === 0) return - const features = removals.map(({ key }) => this.features[key]) - removals.forEach(({ key }) => delete this.features[key]) - this.emit('removefeatures', ({ features })) -} - - -const isGeometry = value => { - if (!value) return false - else if (typeof value !== 'object') return false - else { - if (!value.type) return false - else if (!value.coordinates && !value.geometries) return false - return true - } -} - - -/** - * - */ -FeatureStore.prototype.handleUpdates = function (updates) { - - // We don't want null geometry overwrite existing one - // in case only feature properties were updated (JSON). - // - const trim = properties => { - const { geometry, ...rest } = properties - return geometry - ? properties - : rest - } - - updates.forEach(({ key, value }) => { - const properties = isGeometry(value) - ? { geometry: readGeometry(value) } - : trim(readFeature(value).getProperties()) - - const feature = this.features[key] - feature.setProperties({ ...feature.getProperties(), ...properties }) - }) -} - /** * */ From 13d8eafe4d0dc98754a9a623fd7fc2fdca03b565 Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 27 Jun 2024 17:51:08 +0200 Subject: [PATCH 12/73] playing with signals (WIP). --- package-lock.json | 34 +-- package.json | 5 +- src/renderer/components/Project-services.js | 2 +- src/renderer/components/map/eventHandlers.js | 11 +- src/renderer/ol/style/__LineString.js | 20 ++ src/renderer/ol/style/__Point.js | 26 ++ src/renderer/ol/style/__Polygon.js | 20 ++ src/renderer/ol/style/__Signals.js | 60 ++++ src/renderer/ol/style/__style.js | 13 + src/renderer/ol/style/__styles.js | 14 + src/renderer/ol/style/shared.js | 2 +- src/renderer/ol/style/styleRegistry.js | 141 +++++----- src/renderer/store/FeatureStore.js | 280 ++++++++++--------- src/renderer/store/options/feature.js | 9 +- 14 files changed, 402 insertions(+), 235 deletions(-) create mode 100644 src/renderer/ol/style/__LineString.js create mode 100644 src/renderer/ol/style/__Point.js create mode 100644 src/renderer/ol/style/__Polygon.js create mode 100644 src/renderer/ol/style/__Signals.js create mode 100644 src/renderer/ol/style/__style.js create mode 100644 src/renderer/ol/style/__styles.js diff --git a/package-lock.json b/package-lock.json index cdee0b25..6b98b963 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.0", "@syncpoint/signal": "github:syncpoint/signal#vite", - "@syncpoint/signs": "^1.0.1", + "@syncpoint/signs": "github:syncpoint/signs#vite", "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", "color": "^4.2.3", @@ -45,7 +45,8 @@ "sanitize-filename": "^1.6.3", "subleveldown": "^6.0.1", "throttle-debounce": "^5.0.0", - "typeface-roboto": "^1.1.13" + "typeface-roboto": "^1.1.13", + "uniqolor": "^1.1.1" }, "devDependencies": { "@babel/core": "^7.21.4", @@ -2923,21 +2924,11 @@ "resolved": "git+ssh://git@github.com/syncpoint/signal.git#08cfb29c6bfd715c34fe93744437932db62b20da" }, "node_modules/@syncpoint/signs": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@syncpoint/signs/-/signs-1.0.1.tgz", - "integrity": "sha512-HrVbTs6UHgUvx4rc3Q5gDe3KPGQmaRtXe4a+jPvw9hp1M/C02Y4/18BbnggvmTRjD1WRg3+ff4vBFS2rI3gYzA==", + "version": "1.1.0", + "resolved": "git+ssh://git@github.com/syncpoint/signs.git#c1b8e45b75d3c88e9f2a19a294e7da4834bcd2d6", "dependencies": { - "ramda": "^0.28.0", - "svg-path-bbox": "^1.2.4" - } - }, - "node_modules/@syncpoint/signs/node_modules/ramda": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", - "integrity": "sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/ramda" + "ramda": "^0.30.1", + "svg-path-bbox": "^2.0.0" } }, "node_modules/@syncpoint/wkx": { @@ -15468,9 +15459,9 @@ } }, "node_modules/svg-path-bbox": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/svg-path-bbox/-/svg-path-bbox-1.2.6.tgz", - "integrity": "sha512-jKkQcvBrjUdzDFOIas2E1g5ICVdZ/YdjDjhvwbfXRYmodN3qIR6pBAzQhLEpn11cb1Q2Hn+ufFqER5wDHr6osg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg-path-bbox/-/svg-path-bbox-2.0.0.tgz", + "integrity": "sha512-DP/dcKuwjfJ2GXiM1RsIKcWv+aGazBXTYPuAH9pWYZVm5+pZ6ho70BeLB0inqUGDCCHDmcUlQ2OcLlGuwhmkKQ==", "dependencies": { "svgpath": "^2.6.0" }, @@ -16134,6 +16125,11 @@ "node": ">=4" } }, + "node_modules/uniqolor": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/uniqolor/-/uniqolor-1.1.1.tgz", + "integrity": "sha512-HUwezlXCwm5bzsEXW7AP7ybezH13uWENRgYT+3dOdhJPvpYucSqvIGckMiLn+Uy2j0NVf3fPp43uZ4aun3t4Ww==" + }, "node_modules/unique-filename": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", diff --git a/package.json b/package.json index 04507d4e..95e41980 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.0", "@syncpoint/signal": "github:syncpoint/signal#vite", - "@syncpoint/signs": "^1.0.1", + "@syncpoint/signs": "github:syncpoint/signs#vite", "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", "color": "^4.2.3", @@ -97,6 +97,7 @@ "sanitize-filename": "^1.6.3", "subleveldown": "^6.0.1", "throttle-debounce": "^5.0.0", - "typeface-roboto": "^1.1.13" + "typeface-roboto": "^1.1.13", + "uniqolor": "^1.1.1" } } diff --git a/src/renderer/components/Project-services.js b/src/renderer/components/Project-services.js index d696ee91..b7a4e45c 100644 --- a/src/renderer/components/Project-services.js +++ b/src/renderer/components/Project-services.js @@ -62,7 +62,7 @@ export default async projectUUID => { const coordinatesFormat = new CoordinatesFormat(emitter, preferencesStore) const optionStore = new OptionStore(coordinatesFormat, store, sessionStore) const nominatim = new Nominatim(store) - const featureStore = new FeatureStore(store, selection) + const featureStore = new FeatureStore(store, selection, emitter) const searchIndex = new SearchIndex(jsonDB, documentStore, optionStore, emitter, nominatim, sessionStore, spatialIndex) // Key bindings. diff --git a/src/renderer/components/map/eventHandlers.js b/src/renderer/components/map/eventHandlers.js index 391b5f72..a757cb78 100644 --- a/src/renderer/components/map/eventHandlers.js +++ b/src/renderer/components/map/eventHandlers.js @@ -68,7 +68,7 @@ const sendPreview = (services, map) => { * */ const mapHandlers = (services, map) => { - const { selection, osdDriver, dragAndDrop } = services + const { selection, osdDriver, dragAndDrop, emitter } = services map.addEventListener('keydown', event => { const { key } = event.originalEvent @@ -85,6 +85,15 @@ const mapHandlers = (services, map) => { if (deselect.length) selection.deselect(deselect) }) + let resolution + map.on('moveend', () => { + const updated = map.getView().getResolution() + if (updated !== resolution) { + resolution = updated + emitter.emit('view/resolution', { resolution }) + } + }) + // Note: Neither dragstart nor dragend events are fired when dragging // a file into the browser from the OS. const target = document.getElementById('map') diff --git a/src/renderer/ol/style/__LineString.js b/src/renderer/ol/style/__LineString.js new file mode 100644 index 00000000..aa292aec --- /dev/null +++ b/src/renderer/ol/style/__LineString.js @@ -0,0 +1,20 @@ +import Signal from '@syncpoint/signal' +import * as Signals from './__Signals' +import { style } from './__style' + +const simplifyGeometry = (geometry, resolution) => + geometry.getCoordinates().length > 50 + ? geometry.simplify(resolution) + : geometry + +export const LineString = feature => { + feature.$colorScheme = Signals.$colorScheme(feature) + feature.$schemeStyle = Signals.$schemeStyle(feature) + feature.$effectiveStyle = Signals.$effectiveStyle(feature) + feature.$lineSmoothing = Signals.$lineSmoothing(feature) + feature.$styleRegistry = Signals.$styleRegistry(feature) + feature.$definingGeometry = Signals.$definingGeometry(feature) + feature.$simplifiedGeometry = Signal.link(simplifyGeometry, [feature.$definingGeometry, feature.$resolution]) + feature.$smoothenedGeometry = Signals.$smoothenedGeometry(feature) + feature.$style = feature.$smoothenedGeometry.map(style) +} diff --git a/src/renderer/ol/style/__Point.js b/src/renderer/ol/style/__Point.js new file mode 100644 index 00000000..0b43dc7b --- /dev/null +++ b/src/renderer/ol/style/__Point.js @@ -0,0 +1,26 @@ +import Signal from '@syncpoint/signal' +import * as Signals from './__Signals' +import { styleFactory } from './styleFactory' + +const $style = feature => Signal.link((geometry, symbolModifiers, sidc, styleRegistry) => { + const styleProps = [{ + id: 'style:2525c/symbol', + geometry, + 'symbol-code': sidc, + 'symbol-modifiers': symbolModifiers, + // TODO: selected styles + }] + return styleProps + .map(styleRegistry) + .flatMap(styleFactory) +}, [feature.$definingGeometry, feature.$symbolModifiers, feature.$sidc, feature.$styleRegistry]) + +export const Point = feature => { + feature.$definingGeometry = Signals.$definingGeometry(feature) + feature.$colorScheme = Signals.$colorScheme(feature) + feature.$schemeStyle = Signals.$schemeStyle(feature) + feature.$effectiveStyle = Signals.$effectiveStyle(feature) + feature.$styleRegistry = Signals.$styleRegistry(feature) + feature.$symbolModifiers = Signals.$symbolModifiers(feature) + feature.$style = $style(feature) +} \ No newline at end of file diff --git a/src/renderer/ol/style/__Polygon.js b/src/renderer/ol/style/__Polygon.js new file mode 100644 index 00000000..51b7a66d --- /dev/null +++ b/src/renderer/ol/style/__Polygon.js @@ -0,0 +1,20 @@ +import Signal from '@syncpoint/signal' +import * as Signals from './__Signals' +import { style } from './__style' + +const simplifyGeometry = (geometry, resolution) => + geometry.getCoordinates()[0].length > 50 + ? geometry.simplify(resolution) + : geometry + +export const Polygon = feature => { + feature.$colorScheme = Signals.$colorScheme(feature) + feature.$schemeStyle = Signals.$schemeStyle(feature) + feature.$effectiveStyle = Signals.$effectiveStyle(feature) + feature.$lineSmoothing = Signals.$lineSmoothing(feature) + feature.$styleRegistry = Signals.$styleRegistry(feature) + feature.$definingGeometry = Signals.$definingGeometry(feature) + feature.$simplifiedGeometry = Signal.link(simplifyGeometry, [feature.$definingGeometry, feature.$resolution]) + feature.$smoothenedGeometry = Signals.$smoothenedGeometry(feature) + feature.$style = feature.$smoothenedGeometry.map(style) +} diff --git a/src/renderer/ol/style/__Signals.js b/src/renderer/ol/style/__Signals.js new file mode 100644 index 00000000..525cbb3b --- /dev/null +++ b/src/renderer/ol/style/__Signals.js @@ -0,0 +1,60 @@ +import * as R from 'ramda' +import Signal from '@syncpoint/signal' +import { echelonCode, identityCode, statusCode, parameterized } from '../../symbology/2525c' +import * as Colors from './color-schemes' +import styleRegistry from './styleRegistry' +import { smooth } from './chaikin' +import { MODIFIERS } from '../../symbology/2525c' + + +export const $definingGeometry = feature => feature.$feature.map(feature => feature.getGeometry()) + +export const $smoothenedGeometry = feature => Signal.link((geometry, lineSmoothing) => { + return lineSmoothing ? smooth(geometry) : geometry +}, [feature.$simplifiedGeometry, feature.$lineSmoothing]) + +export const $colorScheme = feature => Signal.link((globalStyle, layerStyle, featureStyle) => { + return featureStyle?.['color-scheme'] || + layerStyle?.['color-scheme'] || + globalStyle?.['color-scheme'] || + 'medium' +}, [feature.$globalStyle, feature.$layerStyle, feature.$featureStyle]) + +export const $schemeStyle = feature => Signal.link((sidc, colorScheme) => { + const status = statusCode(sidc) + const identity = identityCode(sidc) + const simpleIdentity = identity === 'H' || identity === 'S' + ? 'H' + : '-' + + return { + 'binary-color': Colors.lineColor(colorScheme)(simpleIdentity), // black or red + 'line-color': Colors.lineColor(colorScheme)(identity), + 'fill-color': Colors.lineColor(colorScheme)(identity), + 'line-dash-array': status === 'A' ? [20, 10] : null, + 'line-halo-color': Colors.lineHaloColor(identity), + 'line-halo-dash-array': status === 'A' ? [20, 10] : null + } +}, [feature.$sidc, feature.$colorScheme]) + +export const $effectiveStyle = feature => Signal.link((globalStyle, schemeStyle, layerStyle, featureStyle) => { + if (!layerStyle['line-color']) delete layerStyle['line-color'] + if (!layerStyle['line-halo-color']) delete layerStyle['line-halo-color'] + + return { + ...globalStyle, + ...schemeStyle, + ...layerStyle, + ...featureStyle + } + +}, [feature.$globalStyle, feature.$schemeStyle, feature.$layerStyle, feature.$featureStyle]) + +export const $lineSmoothing = feature => feature.$effectiveStyle.map(R.prop('line-smooth')) +export const $styleRegistry = feature => feature.$effectiveStyle.map(styleRegistry) + +export const $symbolModifiers = feature => Signal.link((properties) => { + return Object.entries(properties) + .filter(([key, value]) => MODIFIERS[key] && value) + .reduce((acc, [key, value]) => R.tap(acc => (acc[MODIFIERS[key]] = value), acc), {}) +}, [feature.$properties]) \ No newline at end of file diff --git a/src/renderer/ol/style/__style.js b/src/renderer/ol/style/__style.js new file mode 100644 index 00000000..0b1f82c6 --- /dev/null +++ b/src/renderer/ol/style/__style.js @@ -0,0 +1,13 @@ +import { Circle, Fill, Stroke, Style } from 'ol/style' + +export const style = geometry => { + const fill = new Fill({ color: 'rgba(255,255,255,0.3)' }) + const strokeColor = '#888' + const stroke = new Stroke({ color: strokeColor, width: 1.25 }) + return new Style({ + geometry, + image: new Circle({ fill: fill, stroke: stroke, radius: 5 }), + fill: fill, + stroke: stroke, + }) +} diff --git a/src/renderer/ol/style/__styles.js b/src/renderer/ol/style/__styles.js new file mode 100644 index 00000000..01928699 --- /dev/null +++ b/src/renderer/ol/style/__styles.js @@ -0,0 +1,14 @@ +import Signal from '@syncpoint/signal' +import { Polygon } from './__Polygon' +import { LineString } from './__LineString' +import { Point } from './__Point' +import { style } from './__style' + +const other = feature => feature.$style = Signal.of(style()) + +export const styles = { + Polygon, + LineString, + Point, + other +} diff --git a/src/renderer/ol/style/shared.js b/src/renderer/ol/style/shared.js index 72c66f0b..556706a4 100644 --- a/src/renderer/ol/style/shared.js +++ b/src/renderer/ol/style/shared.js @@ -71,7 +71,7 @@ export const effectiveStyle = [next => { return { smoothen: !!smoothen, - effectiveStyle: styleRegistry(next, props) + effectiveStyle: styleRegistry(props) } }, ['sidc', 'globalStyle', 'layerStyle', 'featureStyle']] diff --git a/src/renderer/ol/style/styleRegistry.js b/src/renderer/ol/style/styleRegistry.js index 9d61bd60..ee087ce5 100644 --- a/src/renderer/ol/style/styleRegistry.js +++ b/src/renderer/ol/style/styleRegistry.js @@ -1,3 +1,5 @@ +import { PI_OVER_4 } from '../../../shared/Math' + const COLOR_WHITE = 'white' const COLOR_BLACK = 'black' const COLOR_YELLOW = 'yellow' @@ -6,85 +8,88 @@ const DASH_ARRAY_10_10 = [10, 10] const DASH_ARRAY_14_6 = [14, 6] const DASH_ARRAY_20_8_2_8 = [20, 8, 2, 8] -export default ({ PI_OVER_4 }, props) => { - - const font = props['text-font'] || [ - props['text-font-style'], - props['text-font-variant'], - props['text-font-weight'], - props['text-font-size'], - props['text-font-family'] +/** + * Registry of predefined styles. + */ +export default (options) => { + + const font = options['text-font'] || [ + options['text-font-style'], + options['text-font-variant'], + options['text-font-weight'], + options['text-font-size'], + options['text-font-family'] ].filter(Boolean).join(' ') const registry = {} registry['style:2525c/symbol'] = { - 'color-scheme': props['color-scheme'], - 'symbol-color': props['symbol-color'], - 'symbol-halo-color': props['symbol-halo-color'], - 'symbol-halo-width': props['symbol-halo-width'], - 'symbol-text-color': props['symbol-text-color'], - 'symbol-text-halo-color': props['symbol-text-halo-color'], - 'symbol-text-halo-width': props['symbol-text-halo-width'], - 'symbol-text-size': props['symbol-text-size'], - 'symbol-text': props['symbol-text'], - 'symbol-fill': props['symbol-fill'], - 'symbol-fill-opacity': props['symbol-fill-opacity'], - 'symbol-frame': props['symbol-frame'], - 'symbol-icon': props['symbol-icon'], - 'symbol-line-width': props['symbol-line-width'], - 'symbol-size': props['symbol-size'], - 'icon-scale': props['icon-scale'] + 'color-scheme': options['color-scheme'], + 'symbol-color': options['symbol-color'], + 'symbol-halo-color': options['symbol-halo-color'], + 'symbol-halo-width': options['symbol-halo-width'], + 'symbol-text-color': options['symbol-text-color'], + 'symbol-text-halo-color': options['symbol-text-halo-color'], + 'symbol-text-halo-width': options['symbol-text-halo-width'], + 'symbol-text-size': options['symbol-text-size'], + 'symbol-text': options['symbol-text'], + 'symbol-fill': options['symbol-fill'], + 'symbol-fill-opacity': options['symbol-fill-opacity'], + 'symbol-frame': options['symbol-frame'], + 'symbol-icon': options['symbol-icon'], + 'symbol-line-width': options['symbol-line-width'], + 'symbol-size': options['symbol-size'], + 'icon-scale': options['icon-scale'] } registry['style:2525c/default-stroke'] = { - 'line-cap': props['line-cap'], - 'line-join': props['line-join'], - 'line-color': props['line-color'], - 'line-width': props['line-width'], - 'line-dash-array': props['line-dash-array'], - 'line-halo-color': props['line-halo-color'], - 'line-halo-width': props['line-halo-width'], - 'line-halo-dash-array': props['line-halo-dash-array'] + 'line-cap': options['line-cap'], + 'line-join': options['line-join'], + 'line-color': options['line-color'], + 'line-width': options['line-width'], + 'line-dash-array': options['line-dash-array'], + 'line-halo-color': options['line-halo-color'], + 'line-halo-width': options['line-halo-width'], + 'line-halo-dash-array': options['line-halo-dash-array'] } registry['style:2525c/solid-stroke'] = { - 'line-cap': props['line-cap'], - 'line-join': props['line-join'], - 'line-color': props['line-color'], - 'line-width': props['line-width'], - 'line-halo-color': props['line-halo-color'], - 'line-halo-width': props['line-halo-width'] + 'line-cap': options['line-cap'], + 'line-join': options['line-join'], + 'line-color': options['line-color'], + 'line-width': options['line-width'], + 'line-halo-color': options['line-halo-color'], + 'line-halo-width': options['line-halo-width'] } registry['style:2525c/dashed-stroke'] = { - 'line-cap': props['line-cap'], - 'line-join': props['line-join'], - 'line-color': props['line-color'], - 'line-width': props['line-width'], + 'line-cap': options['line-cap'], + 'line-join': options['line-join'], + 'line-color': options['line-color'], + 'line-width': options['line-width'], 'line-dash-array': DASH_ARRAY_14_6, - 'line-halo-color': props['line-halo-color'], - 'line-halo-width': props['line-halo-width'], + 'line-halo-color': options['line-halo-color'], + 'line-halo-width': options['line-halo-width'], 'line-halo-dash-array': DASH_ARRAY_14_6 } registry['style:2525c/solid-fill'] = { - 'line-cap': props['line-cap'], - 'line-join': props['line-join'], - 'line-color': props['line-color'], - 'line-width': props['line-width'], - 'line-halo-color': props['line-halo-color'], - 'line-halo-width': props['line-halo-width'], - 'fill-color': props['fill-color'] + 'line-cap': options['line-cap'], + 'line-join': options['line-join'], + 'line-color': options['line-color'], + 'line-width': options['line-width'], + 'line-halo-color': options['line-halo-color'], + 'line-halo-width': options['line-halo-width'], + 'fill-color': options['fill-color'] } registry['style:2525c/hatch-fill'] = { - 'line-cap': props['line-cap'], - 'line-join': props['line-join'], - 'line-color': props['line-color'], - 'line-width': props['line-width'], - 'line-halo-color': props['line-halo-color'], - 'line-halo-width': props['line-halo-width'], + 'line-cap': options['line-cap'], + 'line-join': options['line-join'], + 'line-color': options['line-color'], + 'line-width': options['line-width'], + 'line-halo-color': options['line-halo-color'], + 'line-halo-width': options['line-halo-width'], 'fill-pattern': 'hatch', 'fill-pattern-angle': 45, 'fill-pattern-size': 2, @@ -93,24 +98,24 @@ export default ({ PI_OVER_4 }, props) => { registry['style:default-text'] = { 'text-font': font, - 'text-color': props['text-color'], - 'text-fill-color': props['text-fill-color'], - 'text-line-color': props['text-line-color'], - 'text-line-width': props['text-line-width'], - 'text-halo-color': props['text-halo-color'], - 'text-halo-width': props['text-halo-width'], + 'text-color': options['text-color'], + 'text-fill-color': options['text-fill-color'], + 'text-line-color': options['text-line-color'], + 'text-line-width': options['text-line-width'], + 'text-halo-color': options['text-halo-color'], + 'text-halo-width': options['text-halo-width'], 'text-justify': 'center', 'text-rotation-anchor': 'auto' } registry['style:2525c/fence-stroke'] = { 'line-cap': 'square', - 'line-color': props['binary-color'], + 'line-color': options['binary-color'], 'line-width': 2 } registry['style:2525c/fence-o'] = { - 'shape-line-color': props['binary-color'], + 'shape-line-color': options['binary-color'], 'shape-line-width': 2, 'shape-points': 8, 'shape-radius': 8, @@ -120,7 +125,7 @@ export default ({ PI_OVER_4 }, props) => { } registry['style:2525c/fence-x'] = { - 'shape-line-color': props['binary-color'], + 'shape-line-color': options['binary-color'], 'shape-line-width': 2, 'shape-points': 4, 'shape-radius': 8, @@ -131,10 +136,10 @@ export default ({ PI_OVER_4 }, props) => { registry['style:wasp-stroke'] = { 'line-color': COLOR_YELLOW, - 'line-width': props['line-width'], + 'line-width': options['line-width'], 'line-dash-array': DASH_ARRAY_10_10, 'line-halo-color': COLOR_BLACK, - 'line-halo-width': props['line-halo-width'], + 'line-halo-width': options['line-halo-width'], 'line-halo-dash-array': null } diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index 6230db36..67d836f1 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -5,13 +5,31 @@ import GeoJSON from 'ol/format/GeoJSON' import * as Extent from 'ol/extent' import Signal from '@syncpoint/signal' import Emitter from '../../shared/emitter' -import { geometryType, setCoordinates } from '../model/geometry' +import * as Geometry from '../model/geometry' import * as ID from '../ids' import { reduce, rules } from '../ol/style/rules' import crosshair from '../ol/style/crosshair' import { stylist as measurementStyler } from '../ol/interaction/measure/style' import * as TS from '../ol/ts' import * as Math from '../../shared/Math' +import uniqolor from 'uniqolor' +import {Circle, Fill, Stroke, Style} from 'ol/style' +import uuid from '../../shared/uuid' + +import { styles } from '../ol/style/__styles' + + +const randomStyle = () => { + const fill = new Fill({ color: 'rgba(255,255,255,0.4)' }) + const { color: strokeColor } = uniqolor(uuid()) + const stroke = new Stroke({ color: strokeColor, width: 1.25 }) + + return new Style({ + image: new Circle({ fill: fill, stroke: stroke, radius: 5 }), + fill: fill, + stroke: stroke, + }) +} Signal.split = (conditions, signal) => { const outputs = conditions.map(() => Signal.of()) @@ -61,23 +79,50 @@ const isGeometry = value => { const apply = options => feature => feature && feature.apply(options, true) +// Batch operations order: +// 0 - (del, style+) +// 1 - (del, feature) +// 2 - (put, feature) +// 3 - (put, style+) +const ord = R.cond([ + [R.both(R.propEq('del', 'type'), R.compose(R.startsWith('style+'), R.prop('key'))), R.always(0)], + [R.both(R.propEq('del', 'type'), R.compose(R.startsWith(ID.FEATURE_SCOPE), R.prop('key'))), R.always(1)], + [R.both(R.propEq('put', 'type'), R.compose(R.startsWith(ID.FEATURE_SCOPE), R.prop('key'))), R.always(2)], + [R.both(R.propEq('del', 'type'), R.compose(R.startsWith('style+'), R.prop('key'))), R.always(3)], + [R.T, R.always(4)] +]) + +const assign = (acc, [key, value]) => { + acc[key] = value + return acc +} + +const push = (acc, [key, value]) => { + acc.push({ type: 'put', key, value }) + return acc +} + /** * */ -export function FeatureStore (store, selection) { +export function FeatureStore (store, selection, emitter) { this.store = store this.selection = selection + this.emitter = emitter this.features = {} - this.styleProps = {} + this.styleProperties = {} // global (default), layer and feature style properties // FIXME: Signal should support on/off store.addEventListener = (type, handler) => store.on(type, handler) store.removeEventListener = (type, handler) => store.off(type, handler) selection.addEventListener = (type, handler) => selection.on(type, handler) selection.removeEventListener = (type, handler) => selection.off(type, handler) + emitter.addEventListener = (type, handler) => emitter.on(type, handler) + emitter.removeEventListener = (type, handler) => emitter.off(type, handler) const $operations = R.compose( Signal.flatten, + R.map(R.sort((a, b) => ord(a) - ord(b))), R.map(R.prop('operations')) )(Signal.fromListeners(['batch'], store)) @@ -93,26 +138,24 @@ export function FeatureStore (store, selection) { R.compose(isCandidateId, R.prop('key')) ], $operations) - $globalStyle.on(({ value }) => Object.values(this.features).forEach(apply({ globalStyle: value }))) - $featureStyle.on(({ key, value }) => apply({ featureStyle: value })(this.features[ID.featureId(key)])) + // $globalStyle.on(({ value }) => Object.values(this.features).forEach(apply({ globalStyle: value }))) - $featureStyle.on(console.log) + $featureStyle.on(({ type, key, value }) => { + const feature = this.features[ID.featureId(key)] + if (feature) feature.$featureStyle(type === 'put' ? value : {}) + }) $layerStyle.on(({ type, key, value }) => { - if (type === 'put') this.styleProps[key] = value - else delete this.styleProps[key] - const layerStyle = type === 'put' ? value : {} const layerId = ID.layerId(key) - Object - .keys(this.features) - .filter(key => ID.layerId(key) === layerId) - .forEach(key => apply({ layerStyle })(this.features[key])) + Object.entries(this.features) + .filter(([key, ]) => ID.layerId(key) === layerId) + .forEach(([, feature]) => feature.$layerStyle(type === 'put' ? value : {})) }) const [ - $removal, - $update, - $addition + $featureRemoval, + $featureUpdate, + $featureAddition ] = Signal.split([ R.propEq('del', 'type'), ({ key }) => this.features[key], @@ -128,17 +171,22 @@ export function FeatureStore (store, selection) { : rest } - $removal.on(({ key }) => { + $featureRemoval.on(({ key }) => { const features = [this.features[key]] delete this.features[key] this.emit('removefeatures', ({ features })) }) - $addition.on(({ key, value }) => { - if (isValid) this.addFeatures([readFeature({ id: key, ...value })]) - }) + $featureAddition + .map(({ key, value }) => ({ id: key, ...value })) + .map(readFeature) + // .filter(feature => Geometry.geometryType(feature) === 'Polygon') + // .filter(feature => feature.getGeometry().getCoordinates()[0].length > 80) + // .filter(feature => feature.getProperties().sidc.startsWith('M')) + .map(this.wrapFeature.bind(this)) + .on(feature => this.features[feature.getId()] = feature) - $update.on(({ key, value }) => { + $featureUpdate.on(({ key, value }) => { const properties = isGeometry(value) ? { geometry: readGeometry(value) } : trim(readFeature(value).getProperties()) @@ -147,6 +195,7 @@ export function FeatureStore (store, selection) { feature.setProperties({ ...feature.getProperties(), ...properties }) }) + // TODO: limit to features const $selection = Signal.fromListeners(['selection'], selection) const modes = ({ deselected }) => { const selected = selection.selected() @@ -162,58 +211,46 @@ export function FeatureStore (store, selection) { R.map(modes) )($selection) - $mode.on(console.log) + // $mode.on(console.log) - selection.on('selection', ({ deselected, selected }) => { - deselected.forEach(key => { - if (this.features[key]) this.features[key].apply({ mode: 'default' }) - }) + // selection.on('selection', ({ deselected, selected }) => { + // deselected.forEach(key => { + // if (this.features[key]) this.features[key].apply({ mode: 'default' }) + // }) - const mode = selection.selected().length > 1 - ? 'multiselect' - : 'singleselect' + // const mode = selection.selected().length > 1 + // ? 'multiselect' + // : 'singleselect' - selected.forEach(key => { - if (this.features[key]) this.features[key].apply({ mode }) - }) + // selected.forEach(key => { + // if (this.features[key]) this.features[key].apply({ mode }) + // }) + // }) + + const $resolution = Signal.fromListeners(['view/resolution'], emitter) + $resolution.on(({ resolution }) => { + Object.values(this.features).forEach(feature => feature.$resolution(resolution)) }) } util.inherits(FeatureStore, Emitter) - /** * */ FeatureStore.prototype.bootstrap = async function () { - // On startup: load all features: - // - this.styleProps = Object.fromEntries(await this.store.tuples('style+')) - await this.loadFeatures(ID.FEATURE_SCOPE) - await this.loadFeatures(ID.MARKER_SCOPE) - await this.loadFeatures(ID.MEASURE_SCOPE) -} - -/** - * - */ -FeatureStore.prototype.loadFeatures = async function (scope) { - const tuples = await this.store.tuples(scope) - const geoJSON = tuples.map(([id, feature]) => ({ id, ...feature })) - const isValid = feature => feature?.type === 'Feature' && feature.geometry - const [valid, invalid] = R.partition(isValid, geoJSON) - if (invalid.length) console.warn('invalid features', invalid) - this.addFeatures(readFeatures({ type: 'FeatureCollection', features: valid })) -} + const reduce = async (prefix, fn, acc) => { + const db = this.store.db + const it = db.iterator({ gte: `${prefix}`, lte: `${prefix}\xff` }) + for await (const entry of it) acc = fn(acc, entry) + return acc + } -/** - * - */ -FeatureStore.prototype.addFeatures = function (features) { - const wrap = this.wrap.bind(this) - features.map(wrap).forEach(feature => (this.features[feature.getId()] = feature)) - this.emit('addfeatures', ({ features })) + // styles: Only needed while loading features. + const operations = await reduce('style+', push, []) + await reduce(ID.FEATURE_SCOPE, push, operations) + this.store.emit('batch', { operations }) } @@ -237,40 +274,29 @@ FeatureStore.prototype.feature = function (key) { } -/** - * - */ -FeatureStore.prototype.wrap = function (feature) { - const id = feature.getId() - if (ID.isFeatureId(id)) return this.wrapFeature(feature) - else if (ID.isMarkerId(id)) return this.wrapMarker(feature) - else if (ID.isMeasureId(id)) return this.wrapMeasurement(feature) - else return feature -} - - /** * */ FeatureStore.prototype.wrapFeature = function (feature) { - const type = geometryType(feature.getGeometry()) - if (!rules[type]) console.warn('[style] unsupported geometry', type) - - let state = { - TS, - ...Math, - mode: 'default', - rules: rules[type] || [], - layerStyle: {}, - featureStyle: {} - } - const featureId = feature.getId() const layerId = ID.layerId(featureId) - const set = key => props => (state[key] = props) - R.when(Boolean, set('globalStyle'))(this.styleProps[ID.defaultStyleId]) - R.when(Boolean, set('layerStyle'))(this.styleProps['style+' + layerId]) - R.when(Boolean, set('featureStyle'))(this.styleProps['style+' + featureId]) + + feature.$feature = Signal.of(feature) + feature.$globalStyle = Signal.of({}) + feature.$layerStyle = Signal.of({}) + feature.$featureStyle = Signal.of({}) + feature.$sidc = Signal.map(feature => feature.getProperties().sidc, feature.$feature) + feature.$properties = Signal.map(feature => feature.getProperties(), feature.$feature) + feature.$resolution = Signal.of() + + const geometryType = Geometry.geometryType(feature.getGeometry()) + ;(styles[geometryType] ?? styles.other)(feature) + + feature.$style.on(feature.setStyle.bind(feature)) + + // if (feature.$effectiveStyle) { + // feature.$effectiveStyle.on(console.log) + // } // Use dedicated function to update feature coordinates from within // modify interaction. Such internal changes must not trigger ModifyEvent. @@ -289,54 +315,38 @@ FeatureStore.prototype.wrapFeature = function (feature) { setTimeout(() => feature.dispatchEvent({ type: 'change', target: feature })) } - feature.setStyle((feature, resolution) => { - const { geometry: definingGeometry, ...properties } = feature.getProperties() - state = reduce(state, { - definingGeometry, - properties, - centerResolution: resolution, - geometryKey: `${definingGeometry.ol_uid}:${definingGeometry.getRevision()}`, - geometryType: type - }) - - return state.style - }) - - feature.apply = (obj, forceUpdate) => { - state = reduce(state, obj) - if (forceUpdate) feature.changed() - } - - return feature -} - - -/** - * - */ -FeatureStore.prototype.wrapMarker = function (feature) { - const defaultStyle = crosshair('black') - const selectedStyle = crosshair('red') - - feature.apply = () => {} - feature.setStyle(feature => { - return this.selection.isSelected(feature.getId()) - ? selectedStyle - : defaultStyle - }) - - return feature -} - - -/** - * - */ -FeatureStore.prototype.wrapMeasurement = function (feature) { - const isSelected = feature => this.selection.isSelected(feature.getId()) - - feature.apply = () => {} - feature.setStyle(measurementStyler(isSelected)) + // feature.$sidc.on(x => console.log('[$sidc]', x)) + + // let state = { + // TS, + // ...Math, + // mode: 'default', + // rules: rules[type] || [], + // globalStyle: this.styleProperties[ID.defaultStyleId] ?? {}, + // layerStyle: this.styleProperties['style+' + layerId] ?? {}, + // featureStyle: this.styleProperties['style+' + featureId] ?? {} + // } + + + // const styleFN = (feature, resolution) => { + // const { geometry: definingGeometry, ...properties } = feature.getProperties() + // state = reduce(state, { + // definingGeometry, + // properties, + // centerResolution: resolution, + // geometryKey: `${definingGeometry.ol_uid}:${definingGeometry.getRevision()}`, + // geometryType: type + // }) + + // return state.style + // } + + // feature.setStyle(randomStyle()) + + // feature.apply = (obj, forceUpdate) => { + // state = reduce(state, obj) + // if (forceUpdate) feature.changed() + // } return feature } diff --git a/src/renderer/store/options/feature.js b/src/renderer/store/options/feature.js index 0503c5da..f7f97c2c 100644 --- a/src/renderer/store/options/feature.js +++ b/src/renderer/store/options/feature.js @@ -32,18 +32,11 @@ export default async function (id) { ? `SYSTEM:${geometryType.toLowerCase()}` : `SYSTEM:${geometryType.toLowerCase()}:NONE` - let icon - try { - icon = svg(sidc) - } catch (err) { - console.error(err) - } - return { id, title: feature.name || properties.t || null, // might be undefined description, - svg: icon, + svg: svg(sidc), tags: [ 'SCOPE:FEATURE', hidden ? 'SYSTEM:HIDDEN::mdiEyeOff' : 'SYSTEM:VISIBLE::mdiEyeOutline', From b1436eb6640913c2dad6cfec18724fb3fec736cb Mon Sep 17 00:00:00 2001 From: dehmer Date: Fri, 28 Jun 2024 10:07:42 +0200 Subject: [PATCH 13/73] playing with signals (WIP). --- src/renderer/ol/style/__LineString.js | 11 ++++++ src/renderer/ol/style/__Point.js | 4 ++ src/renderer/ol/style/__Polygon.js | 4 ++ src/renderer/ol/style/__Signals.js | 5 ++- src/renderer/ol/style/linestring-placement.js | 3 +- src/renderer/ol/style/polygon-placement.js | 3 +- src/renderer/store/FeatureStore.js | 32 ++++----------- src/shared/signal.js | 39 +++++++++++++++++++ 8 files changed, 73 insertions(+), 28 deletions(-) create mode 100644 src/shared/signal.js diff --git a/src/renderer/ol/style/__LineString.js b/src/renderer/ol/style/__LineString.js index aa292aec..33ec6106 100644 --- a/src/renderer/ol/style/__LineString.js +++ b/src/renderer/ol/style/__LineString.js @@ -1,19 +1,30 @@ import Signal from '@syncpoint/signal' import * as Signals from './__Signals' import { style } from './__style' +import { placement } from './linestring-placement' +import { labels } from './linestring-styles/labels' const simplifyGeometry = (geometry, resolution) => geometry.getCoordinates().length > 50 ? geometry.simplify(resolution) : geometry +const $labels = feature => feature.$parameterizedSIDC.map(sidc => labels[sidc] || []) +const $placement = feature => feature.$definingGeometry.map(placement) + export const LineString = feature => { + feature.$properties = Signals.$properties(feature) + feature.$modifiers = Signals.$modifiers(feature) + feature.$sidc = Signals.$sidc(feature) + feature.$parameterizedSIDC = Signals.$parameterizedSIDC(feature) + feature.$labels = $labels(feature) feature.$colorScheme = Signals.$colorScheme(feature) feature.$schemeStyle = Signals.$schemeStyle(feature) feature.$effectiveStyle = Signals.$effectiveStyle(feature) feature.$lineSmoothing = Signals.$lineSmoothing(feature) feature.$styleRegistry = Signals.$styleRegistry(feature) feature.$definingGeometry = Signals.$definingGeometry(feature) + // feature.$placement = $placement(feature) feature.$simplifiedGeometry = Signal.link(simplifyGeometry, [feature.$definingGeometry, feature.$resolution]) feature.$smoothenedGeometry = Signals.$smoothenedGeometry(feature) feature.$style = feature.$smoothenedGeometry.map(style) diff --git a/src/renderer/ol/style/__Point.js b/src/renderer/ol/style/__Point.js index 0b43dc7b..e5c6e6f1 100644 --- a/src/renderer/ol/style/__Point.js +++ b/src/renderer/ol/style/__Point.js @@ -16,6 +16,10 @@ const $style = feature => Signal.link((geometry, symbolModifiers, sidc, styleReg }, [feature.$definingGeometry, feature.$symbolModifiers, feature.$sidc, feature.$styleRegistry]) export const Point = feature => { + feature.$properties = Signals.$properties(feature) + feature.$modifiers = Signals.$modifiers(feature) + feature.$sidc = Signals.$sidc(feature) + feature.$parameterizedSIDC = Signals.$parameterizedSIDC(feature) feature.$definingGeometry = Signals.$definingGeometry(feature) feature.$colorScheme = Signals.$colorScheme(feature) feature.$schemeStyle = Signals.$schemeStyle(feature) diff --git a/src/renderer/ol/style/__Polygon.js b/src/renderer/ol/style/__Polygon.js index 51b7a66d..ca822a55 100644 --- a/src/renderer/ol/style/__Polygon.js +++ b/src/renderer/ol/style/__Polygon.js @@ -8,6 +8,10 @@ const simplifyGeometry = (geometry, resolution) => : geometry export const Polygon = feature => { + feature.$properties = Signals.$properties(feature) + feature.$modifiers = Signals.$modifiers(feature) + feature.$sidc = Signals.$sidc(feature) + feature.$parameterizedSIDC = Signals.$parameterizedSIDC(feature) feature.$colorScheme = Signals.$colorScheme(feature) feature.$schemeStyle = Signals.$schemeStyle(feature) feature.$effectiveStyle = Signals.$effectiveStyle(feature) diff --git a/src/renderer/ol/style/__Signals.js b/src/renderer/ol/style/__Signals.js index 525cbb3b..2eee44a5 100644 --- a/src/renderer/ol/style/__Signals.js +++ b/src/renderer/ol/style/__Signals.js @@ -6,7 +6,10 @@ import styleRegistry from './styleRegistry' import { smooth } from './chaikin' import { MODIFIERS } from '../../symbology/2525c' - +export const $properties = feature => feature.$feature.map(feature => feature.getProperties()) +export const $sidc = feature => feature.$properties.map(R.prop('sidc'), ) +export const $parameterizedSIDC = feature => feature.$sidc.map(parameterized) +export const $modifiers = feature => feature.$properties.map(({ sidc, ...rest }) => rest) export const $definingGeometry = feature => feature.$feature.map(feature => feature.getGeometry()) export const $smoothenedGeometry = feature => Signal.link((geometry, lineSmoothing) => { diff --git a/src/renderer/ol/style/linestring-placement.js b/src/renderer/ol/style/linestring-placement.js index c2a8e7a0..71dedfe2 100644 --- a/src/renderer/ol/style/linestring-placement.js +++ b/src/renderer/ol/style/linestring-placement.js @@ -1,6 +1,7 @@ import * as R from 'ramda' +import * as TS from '../ts' -export const placement = ({ TS, geometry }) => { +export const placement = (geometry) => { const segments = TS.segments(geometry) const line = TS.lengthIndexedLine(geometry) const endIndex = line.getEndIndex() diff --git a/src/renderer/ol/style/polygon-placement.js b/src/renderer/ol/style/polygon-placement.js index f8b5a449..93ba8d65 100644 --- a/src/renderer/ol/style/polygon-placement.js +++ b/src/renderer/ol/style/polygon-placement.js @@ -1,3 +1,4 @@ +import * as TS from '../ts' const lazy = function (fn) { let evaluated = false @@ -11,7 +12,7 @@ const lazy = function (fn) { } } -export const placement = ({ TS, geometry }) => { +export const placement = (geometry) => { const ring = geometry.getExteriorRing() const envelope = ring.getEnvelopeInternal() const centroid = TS.centroid(ring) diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index 67d836f1..9bc6d66d 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -5,13 +5,12 @@ import GeoJSON from 'ol/format/GeoJSON' import * as Extent from 'ol/extent' import Signal from '@syncpoint/signal' import Emitter from '../../shared/emitter' +import { flatten, select } from '../../shared/signal' import * as Geometry from '../model/geometry' import * as ID from '../ids' import { reduce, rules } from '../ol/style/rules' import crosshair from '../ol/style/crosshair' import { stylist as measurementStyler } from '../ol/interaction/measure/style' -import * as TS from '../ol/ts' -import * as Math from '../../shared/Math' import uniqolor from 'uniqolor' import {Circle, Fill, Stroke, Style} from 'ol/style' import uuid from '../../shared/uuid' @@ -31,21 +30,6 @@ const randomStyle = () => { }) } -Signal.split = (conditions, signal) => { - const outputs = conditions.map(() => Signal.of()) - signal.on(value => { - const match = condition => condition(value) - outputs[conditions.findIndex(match)]?.(value) - }) - return outputs -} - -Signal.flatten = signal => { - const output = Signal.of() - signal.on(v => (Array.isArray(v) ? v : [v]).forEach(output)) - return output -} - const format = new GeoJSON({ dataProjection: 'EPSG:3857', featureProjection: 'EPSG:3857' @@ -121,7 +105,7 @@ export function FeatureStore (store, selection, emitter) { emitter.removeEventListener = (type, handler) => emitter.off(type, handler) const $operations = R.compose( - Signal.flatten, + flatten, R.map(R.sort((a, b) => ord(a) - ord(b))), R.map(R.prop('operations')) )(Signal.fromListeners(['batch'], store)) @@ -131,7 +115,7 @@ export function FeatureStore (store, selection, emitter) { $featureStyle, $layerStyle, $feature - ] = Signal.split([ + ] = select([ R.propEq(ID.defaultStyleId, 'key'), R.compose(ID.isFeatureStyleId, R.prop('key')), R.compose(ID.isLayerStyleId, R.prop('key')), @@ -156,7 +140,7 @@ export function FeatureStore (store, selection, emitter) { $featureRemoval, $featureUpdate, $featureAddition - ] = Signal.split([ + ] = select([ R.propEq('del', 'type'), ({ key }) => this.features[key], R.T @@ -207,7 +191,7 @@ export function FeatureStore (store, selection, emitter) { } const $mode = R.compose( - Signal.flatten, + flatten, R.map(modes) )($selection) @@ -285,8 +269,6 @@ FeatureStore.prototype.wrapFeature = function (feature) { feature.$globalStyle = Signal.of({}) feature.$layerStyle = Signal.of({}) feature.$featureStyle = Signal.of({}) - feature.$sidc = Signal.map(feature => feature.getProperties().sidc, feature.$feature) - feature.$properties = Signal.map(feature => feature.getProperties(), feature.$feature) feature.$resolution = Signal.of() const geometryType = Geometry.geometryType(feature.getGeometry()) @@ -294,8 +276,8 @@ FeatureStore.prototype.wrapFeature = function (feature) { feature.$style.on(feature.setStyle.bind(feature)) - // if (feature.$effectiveStyle) { - // feature.$effectiveStyle.on(console.log) + // if (feature.$labels) { + // feature.$labels.on(console.log) // } // Use dedicated function to update feature coordinates from within diff --git a/src/shared/signal.js b/src/shared/signal.js new file mode 100644 index 00000000..e04f9a65 --- /dev/null +++ b/src/shared/signal.js @@ -0,0 +1,39 @@ +import * as R from 'ramda' +import Signal from '@syncpoint/signal' + +// TODO: move to @syncpoint/signal + +/** + * select :: Signal S => [a -> Boolean] -> S a -> [S a] + * + * Split one input signal into multiply output signals based on conditions. + * Each input value is either forwarded to one output signal or dropped if + * no condition matches. + */ +export const select = R.curry((conditions, signal) => { + const outputs = conditions.map(() => Signal.of()) + signal.on(value => { + const match = condition => condition(value) + outputs[conditions.findIndex(match)]?.(value) + }) + return outputs +}) + +export const flatten = signal => { + const output = Signal.of() + signal.on(v => (Array.isArray(v) ? v : [v]).forEach(output)) + return output +} + +/** + * split :: Signal S => [a -> b] -> S a -> [S b] + * + * Split one input signal into multiple output signals based on mapping + * functions. Input values are mapped and forwarded to the function's + * respective output signal. + */ +export const split = R.curry((fns, signal) => { + const outputs = fns.map(() => Signal.of()) + signal.on(value => fns.forEach((fn, i) => outputs[i](fn(value)))) + return outputs +}) \ No newline at end of file From a3c12bbcdc36abc24a3a45bbae276b5a31bfed8d Mon Sep 17 00:00:00 2001 From: dehmer Date: Fri, 28 Jun 2024 11:57:56 +0200 Subject: [PATCH 14/73] playing with signals (WIP). --- src/renderer/ol/style/__LineString.js | 28 ++------- src/renderer/ol/style/__Point.js | 22 +++---- src/renderer/ol/style/__Polygon.js | 23 +++----- src/renderer/ol/style/__Signals.js | 63 -------------------- src/renderer/ol/style/__style.js | 6 +- src/renderer/ol/style/__styles.js | 83 +++++++++++++++++++++++++-- src/renderer/store/FeatureStore.js | 64 +-------------------- 7 files changed, 104 insertions(+), 185 deletions(-) delete mode 100644 src/renderer/ol/style/__Signals.js diff --git a/src/renderer/ol/style/__LineString.js b/src/renderer/ol/style/__LineString.js index 33ec6106..4db42ebe 100644 --- a/src/renderer/ol/style/__LineString.js +++ b/src/renderer/ol/style/__LineString.js @@ -1,31 +1,15 @@ -import Signal from '@syncpoint/signal' -import * as Signals from './__Signals' import { style } from './__style' -import { placement } from './linestring-placement' -import { labels } from './linestring-styles/labels' +import { smooth } from './chaikin' const simplifyGeometry = (geometry, resolution) => geometry.getCoordinates().length > 50 ? geometry.simplify(resolution) : geometry -const $labels = feature => feature.$parameterizedSIDC.map(sidc => labels[sidc] || []) -const $placement = feature => feature.$definingGeometry.map(placement) +const smoothenGeometry = geometry => smooth(geometry) -export const LineString = feature => { - feature.$properties = Signals.$properties(feature) - feature.$modifiers = Signals.$modifiers(feature) - feature.$sidc = Signals.$sidc(feature) - feature.$parameterizedSIDC = Signals.$parameterizedSIDC(feature) - feature.$labels = $labels(feature) - feature.$colorScheme = Signals.$colorScheme(feature) - feature.$schemeStyle = Signals.$schemeStyle(feature) - feature.$effectiveStyle = Signals.$effectiveStyle(feature) - feature.$lineSmoothing = Signals.$lineSmoothing(feature) - feature.$styleRegistry = Signals.$styleRegistry(feature) - feature.$definingGeometry = Signals.$definingGeometry(feature) - // feature.$placement = $placement(feature) - feature.$simplifiedGeometry = Signal.link(simplifyGeometry, [feature.$definingGeometry, feature.$resolution]) - feature.$smoothenedGeometry = Signals.$smoothenedGeometry(feature) - feature.$style = feature.$smoothenedGeometry.map(style) +export default { + simplifyGeometry, + smoothenGeometry, + style: feature => feature.$smoothenedGeometry.map(style) } diff --git a/src/renderer/ol/style/__Point.js b/src/renderer/ol/style/__Point.js index e5c6e6f1..b6b38cb8 100644 --- a/src/renderer/ol/style/__Point.js +++ b/src/renderer/ol/style/__Point.js @@ -1,8 +1,8 @@ +import * as R from 'ramda' import Signal from '@syncpoint/signal' -import * as Signals from './__Signals' import { styleFactory } from './styleFactory' -const $style = feature => Signal.link((geometry, symbolModifiers, sidc, styleRegistry) => { +const style = feature => Signal.link((geometry, symbolModifiers, sidc, styleRegistry) => { const styleProps = [{ id: 'style:2525c/symbol', geometry, @@ -15,16 +15,8 @@ const $style = feature => Signal.link((geometry, symbolModifiers, sidc, styleReg .flatMap(styleFactory) }, [feature.$definingGeometry, feature.$symbolModifiers, feature.$sidc, feature.$styleRegistry]) -export const Point = feature => { - feature.$properties = Signals.$properties(feature) - feature.$modifiers = Signals.$modifiers(feature) - feature.$sidc = Signals.$sidc(feature) - feature.$parameterizedSIDC = Signals.$parameterizedSIDC(feature) - feature.$definingGeometry = Signals.$definingGeometry(feature) - feature.$colorScheme = Signals.$colorScheme(feature) - feature.$schemeStyle = Signals.$schemeStyle(feature) - feature.$effectiveStyle = Signals.$effectiveStyle(feature) - feature.$styleRegistry = Signals.$styleRegistry(feature) - feature.$symbolModifiers = Signals.$symbolModifiers(feature) - feature.$style = $style(feature) -} \ No newline at end of file +export default { + simplifyGeometry: R.identity, + smoothenGeometry: R.identity, + style +} diff --git a/src/renderer/ol/style/__Polygon.js b/src/renderer/ol/style/__Polygon.js index ca822a55..6a25df9e 100644 --- a/src/renderer/ol/style/__Polygon.js +++ b/src/renderer/ol/style/__Polygon.js @@ -1,24 +1,15 @@ -import Signal from '@syncpoint/signal' -import * as Signals from './__Signals' import { style } from './__style' +import { smooth } from './chaikin' const simplifyGeometry = (geometry, resolution) => geometry.getCoordinates()[0].length > 50 ? geometry.simplify(resolution) : geometry -export const Polygon = feature => { - feature.$properties = Signals.$properties(feature) - feature.$modifiers = Signals.$modifiers(feature) - feature.$sidc = Signals.$sidc(feature) - feature.$parameterizedSIDC = Signals.$parameterizedSIDC(feature) - feature.$colorScheme = Signals.$colorScheme(feature) - feature.$schemeStyle = Signals.$schemeStyle(feature) - feature.$effectiveStyle = Signals.$effectiveStyle(feature) - feature.$lineSmoothing = Signals.$lineSmoothing(feature) - feature.$styleRegistry = Signals.$styleRegistry(feature) - feature.$definingGeometry = Signals.$definingGeometry(feature) - feature.$simplifiedGeometry = Signal.link(simplifyGeometry, [feature.$definingGeometry, feature.$resolution]) - feature.$smoothenedGeometry = Signals.$smoothenedGeometry(feature) - feature.$style = feature.$smoothenedGeometry.map(style) +const smoothenGeometry = geometry => smooth(geometry) + +export default { + simplifyGeometry, + smoothenGeometry, + style: feature => feature.$smoothenedGeometry.map(style) } diff --git a/src/renderer/ol/style/__Signals.js b/src/renderer/ol/style/__Signals.js deleted file mode 100644 index 2eee44a5..00000000 --- a/src/renderer/ol/style/__Signals.js +++ /dev/null @@ -1,63 +0,0 @@ -import * as R from 'ramda' -import Signal from '@syncpoint/signal' -import { echelonCode, identityCode, statusCode, parameterized } from '../../symbology/2525c' -import * as Colors from './color-schemes' -import styleRegistry from './styleRegistry' -import { smooth } from './chaikin' -import { MODIFIERS } from '../../symbology/2525c' - -export const $properties = feature => feature.$feature.map(feature => feature.getProperties()) -export const $sidc = feature => feature.$properties.map(R.prop('sidc'), ) -export const $parameterizedSIDC = feature => feature.$sidc.map(parameterized) -export const $modifiers = feature => feature.$properties.map(({ sidc, ...rest }) => rest) -export const $definingGeometry = feature => feature.$feature.map(feature => feature.getGeometry()) - -export const $smoothenedGeometry = feature => Signal.link((geometry, lineSmoothing) => { - return lineSmoothing ? smooth(geometry) : geometry -}, [feature.$simplifiedGeometry, feature.$lineSmoothing]) - -export const $colorScheme = feature => Signal.link((globalStyle, layerStyle, featureStyle) => { - return featureStyle?.['color-scheme'] || - layerStyle?.['color-scheme'] || - globalStyle?.['color-scheme'] || - 'medium' -}, [feature.$globalStyle, feature.$layerStyle, feature.$featureStyle]) - -export const $schemeStyle = feature => Signal.link((sidc, colorScheme) => { - const status = statusCode(sidc) - const identity = identityCode(sidc) - const simpleIdentity = identity === 'H' || identity === 'S' - ? 'H' - : '-' - - return { - 'binary-color': Colors.lineColor(colorScheme)(simpleIdentity), // black or red - 'line-color': Colors.lineColor(colorScheme)(identity), - 'fill-color': Colors.lineColor(colorScheme)(identity), - 'line-dash-array': status === 'A' ? [20, 10] : null, - 'line-halo-color': Colors.lineHaloColor(identity), - 'line-halo-dash-array': status === 'A' ? [20, 10] : null - } -}, [feature.$sidc, feature.$colorScheme]) - -export const $effectiveStyle = feature => Signal.link((globalStyle, schemeStyle, layerStyle, featureStyle) => { - if (!layerStyle['line-color']) delete layerStyle['line-color'] - if (!layerStyle['line-halo-color']) delete layerStyle['line-halo-color'] - - return { - ...globalStyle, - ...schemeStyle, - ...layerStyle, - ...featureStyle - } - -}, [feature.$globalStyle, feature.$schemeStyle, feature.$layerStyle, feature.$featureStyle]) - -export const $lineSmoothing = feature => feature.$effectiveStyle.map(R.prop('line-smooth')) -export const $styleRegistry = feature => feature.$effectiveStyle.map(styleRegistry) - -export const $symbolModifiers = feature => Signal.link((properties) => { - return Object.entries(properties) - .filter(([key, value]) => MODIFIERS[key] && value) - .reduce((acc, [key, value]) => R.tap(acc => (acc[MODIFIERS[key]] = value), acc), {}) -}, [feature.$properties]) \ No newline at end of file diff --git a/src/renderer/ol/style/__style.js b/src/renderer/ol/style/__style.js index 0b1f82c6..05f409ee 100644 --- a/src/renderer/ol/style/__style.js +++ b/src/renderer/ol/style/__style.js @@ -6,8 +6,8 @@ export const style = geometry => { const stroke = new Stroke({ color: strokeColor, width: 1.25 }) return new Style({ geometry, - image: new Circle({ fill: fill, stroke: stroke, radius: 5 }), - fill: fill, - stroke: stroke, + image: new Circle({ fill, stroke, radius: 5 }), + fill, + stroke, }) } diff --git a/src/renderer/ol/style/__styles.js b/src/renderer/ol/style/__styles.js index 01928699..c0eb3fdc 100644 --- a/src/renderer/ol/style/__styles.js +++ b/src/renderer/ol/style/__styles.js @@ -1,14 +1,87 @@ +import * as R from 'ramda' import Signal from '@syncpoint/signal' -import { Polygon } from './__Polygon' -import { LineString } from './__LineString' -import { Point } from './__Point' +import Polygon from './__Polygon' +import LineString from './__LineString' +import Point from './__Point' import { style } from './__style' +import { echelonCode, identityCode, statusCode, parameterized, MODIFIERS } from '../../symbology/2525c' +import * as Colors from './color-schemes' +import * as Geometry from '../../model/geometry' +import styleRegistry from './styleRegistry' -const other = feature => feature.$style = Signal.of(style()) +const other = { + simplifyGeometry: R.identity, + smoothenGeometry: R.identity, + style: () => Signal.of(style()) +} -export const styles = { +const geometryHooks = { Polygon, LineString, Point, other } + +export const $style = feature => { + const geometryType = Geometry.geometryType(feature.getGeometry()) + const hooks = geometryHooks[geometryType] ?? geometryHooks.other + + feature.$definingGeometry = feature.$feature.map(feature => feature.getGeometry()) + feature.$properties = feature.$feature.map(feature => feature.getProperties()) + feature.$modifiers = feature.$properties.map(({ sidc, ...modifiers }) => modifiers) + feature.$sidc = feature.$properties.map(R.prop('sidc')) + + feature.$symbolModifiers = Signal.link((properties) => { + return Object.entries(properties) + .filter(([key, value]) => MODIFIERS[key] && value) + .reduce((acc, [key, value]) => R.tap(acc => (acc[MODIFIERS[key]] = value), acc), {}) + }, [feature.$properties]) + + feature.$parameterizedSIDC = feature.$sidc.map(parameterized) + + feature.$colorScheme = Signal.link((globalStyle, layerStyle, featureStyle) => { + return featureStyle?.['color-scheme'] || + layerStyle?.['color-scheme'] || + globalStyle?.['color-scheme'] || + 'medium' + }, [feature.$globalStyle, feature.$layerStyle, feature.$featureStyle]) + + feature.$schemeStyle = Signal.link((sidc, colorScheme) => { + const status = statusCode(sidc) + const identity = identityCode(sidc) + const simpleIdentity = identity === 'H' || identity === 'S' + ? 'H' + : '-' + + return { + 'binary-color': Colors.lineColor(colorScheme)(simpleIdentity), // black or red + 'line-color': Colors.lineColor(colorScheme)(identity), + 'fill-color': Colors.lineColor(colorScheme)(identity), + 'line-dash-array': status === 'A' ? [20, 10] : null, + 'line-halo-color': Colors.lineHaloColor(identity), + 'line-halo-dash-array': status === 'A' ? [20, 10] : null + } + }, [feature.$sidc, feature.$colorScheme]) + + feature.$effectiveStyle = Signal.link((globalStyle, schemeStyle, layerStyle, featureStyle) => { + if (!layerStyle['line-color']) delete layerStyle['line-color'] + if (!layerStyle['line-halo-color']) delete layerStyle['line-halo-color'] + + return { + ...globalStyle, + ...schemeStyle, + ...layerStyle, + ...featureStyle + } + }, [feature.$globalStyle, feature.$schemeStyle, feature.$layerStyle, feature.$featureStyle]) + + feature.$lineSmoothing = feature.$effectiveStyle.map(R.prop('line-smooth')) + feature.$styleRegistry = feature.$effectiveStyle.map(styleRegistry) + feature.$simplifiedGeometry = Signal.link(hooks.simplifyGeometry, [feature.$definingGeometry, feature.$resolution]) + + feature.$smoothenedGeometry = Signal.link((simplifiedGeometry, lineSmoothing) => { + return lineSmoothing ? hooks.smoothenGeometry(simplifiedGeometry) : simplifiedGeometry + }, [feature.$simplifiedGeometry, feature.$lineSmoothing]) + + return hooks.style(feature) +} diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index 9bc6d66d..62f253bf 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -5,30 +5,10 @@ import GeoJSON from 'ol/format/GeoJSON' import * as Extent from 'ol/extent' import Signal from '@syncpoint/signal' import Emitter from '../../shared/emitter' -import { flatten, select } from '../../shared/signal' -import * as Geometry from '../model/geometry' +import { flatten, select, split } from '../../shared/signal' import * as ID from '../ids' -import { reduce, rules } from '../ol/style/rules' -import crosshair from '../ol/style/crosshair' -import { stylist as measurementStyler } from '../ol/interaction/measure/style' -import uniqolor from 'uniqolor' -import {Circle, Fill, Stroke, Style} from 'ol/style' -import uuid from '../../shared/uuid' +import { $style } from '../ol/style/__styles' -import { styles } from '../ol/style/__styles' - - -const randomStyle = () => { - const fill = new Fill({ color: 'rgba(255,255,255,0.4)' }) - const { color: strokeColor } = uniqolor(uuid()) - const stroke = new Stroke({ color: strokeColor, width: 1.25 }) - - return new Style({ - image: new Circle({ fill: fill, stroke: stroke, radius: 5 }), - fill: fill, - stroke: stroke, - }) -} const format = new GeoJSON({ dataProjection: 'EPSG:3857', @@ -271,14 +251,9 @@ FeatureStore.prototype.wrapFeature = function (feature) { feature.$featureStyle = Signal.of({}) feature.$resolution = Signal.of() - const geometryType = Geometry.geometryType(feature.getGeometry()) - ;(styles[geometryType] ?? styles.other)(feature) - + feature.$style = $style(feature) feature.$style.on(feature.setStyle.bind(feature)) - // if (feature.$labels) { - // feature.$labels.on(console.log) - // } // Use dedicated function to update feature coordinates from within // modify interaction. Such internal changes must not trigger ModifyEvent. @@ -297,38 +272,5 @@ FeatureStore.prototype.wrapFeature = function (feature) { setTimeout(() => feature.dispatchEvent({ type: 'change', target: feature })) } - // feature.$sidc.on(x => console.log('[$sidc]', x)) - - // let state = { - // TS, - // ...Math, - // mode: 'default', - // rules: rules[type] || [], - // globalStyle: this.styleProperties[ID.defaultStyleId] ?? {}, - // layerStyle: this.styleProperties['style+' + layerId] ?? {}, - // featureStyle: this.styleProperties['style+' + featureId] ?? {} - // } - - - // const styleFN = (feature, resolution) => { - // const { geometry: definingGeometry, ...properties } = feature.getProperties() - // state = reduce(state, { - // definingGeometry, - // properties, - // centerResolution: resolution, - // geometryKey: `${definingGeometry.ol_uid}:${definingGeometry.getRevision()}`, - // geometryType: type - // }) - - // return state.style - // } - - // feature.setStyle(randomStyle()) - - // feature.apply = (obj, forceUpdate) => { - // state = reduce(state, obj) - // if (forceUpdate) feature.changed() - // } - return feature } From c99e30cd60903baabb020e17ec14468012f901b7 Mon Sep 17 00:00:00 2001 From: dehmer Date: Sat, 29 Jun 2024 17:42:11 +0200 Subject: [PATCH 15/73] playing with signals (WIP). --- src/renderer/ol/style/__LineString.js | 26 +++- src/renderer/ol/style/__Point.js | 17 ++- src/renderer/ol/style/__Polygon.js | 16 ++- src/renderer/ol/style/__style.js | 2 +- src/renderer/ol/style/__styles.js | 123 ++++++++++--------- src/renderer/ol/style/_colorScheme.js | 10 ++ src/renderer/ol/style/_effectiveStyle.js | 14 +++ src/renderer/ol/style/_labelReplacements.js | 9 ++ src/renderer/ol/style/_schemeStyle.js | 22 ++++ src/renderer/ol/style/_smoothenedGeometry.js | 8 ++ src/renderer/ol/style/_symbolModifers.js | 11 ++ src/renderer/ol/style/_transform.js | 8 ++ src/renderer/ol/style/linestring.js | 6 - src/renderer/store/FeatureStore.js | 74 ++++++----- src/shared/signal.js | 14 ++- 15 files changed, 246 insertions(+), 114 deletions(-) create mode 100644 src/renderer/ol/style/_colorScheme.js create mode 100644 src/renderer/ol/style/_effectiveStyle.js create mode 100644 src/renderer/ol/style/_labelReplacements.js create mode 100644 src/renderer/ol/style/_schemeStyle.js create mode 100644 src/renderer/ol/style/_smoothenedGeometry.js create mode 100644 src/renderer/ol/style/_symbolModifers.js create mode 100644 src/renderer/ol/style/_transform.js diff --git a/src/renderer/ol/style/__LineString.js b/src/renderer/ol/style/__LineString.js index 4db42ebe..f750bde1 100644 --- a/src/renderer/ol/style/__LineString.js +++ b/src/renderer/ol/style/__LineString.js @@ -1,5 +1,11 @@ -import { style } from './__style' +import Signal from '@syncpoint/signal' +import * as TS from '../ts' +import * as Math from '../../../shared/Math' import { smooth } from './chaikin' +import { labels } from './linestring-styles/labels' +import styles from './linestring-styles' +import { styleFactory } from './styleFactory' +import { placement } from './linestring-placement' const simplifyGeometry = (geometry, resolution) => geometry.getCoordinates().length > 50 @@ -8,8 +14,24 @@ const simplifyGeometry = (geometry, resolution) => const smoothenGeometry = geometry => smooth(geometry) +const mainStyles = $ => Signal.link((geometry, sidc, resolution) => { + const context = { geometry, resolution, TS, ...Math } + return (styles[sidc] || styles.DEFAULT)(context) +}, [$.geometry, $.parameterizedSIDC, $.resolution]) + +const style = $ => Signal.link((geometry, sidc, write, resolution, styleRegistry) => { + const context = { geometry, resolution, TS, ...Math } + return (styles[sidc] || styles.DEFAULT)(context) + .map(({ geometry, ...rest }) => ({ geometry: write(geometry), ...rest })) + .map(styleRegistry) + .flatMap(styleFactory) +}, [$.geometry, $.parameterizedSIDC, $.write, $.resolution, $.styleRegistry]) + export default { simplifyGeometry, smoothenGeometry, - style: feature => feature.$smoothenedGeometry.map(style) + labels: parameterizedSIDC => labels[parameterizedSIDC] || [], + labelPlacement: placement, + mainStyles, + style } diff --git a/src/renderer/ol/style/__Point.js b/src/renderer/ol/style/__Point.js index b6b38cb8..07761f38 100644 --- a/src/renderer/ol/style/__Point.js +++ b/src/renderer/ol/style/__Point.js @@ -2,21 +2,30 @@ import * as R from 'ramda' import Signal from '@syncpoint/signal' import { styleFactory } from './styleFactory' -const style = feature => Signal.link((geometry, symbolModifiers, sidc, styleRegistry) => { +const mainStyles = $ => Signal.link((geometry, symbolModifiers, sidc) => [{ + id: 'style:2525c/symbol', + geometry, + 'symbol-code': sidc, + 'symbol-modifiers': symbolModifiers +}], [$.geometry, $.symbolModifiers, $.sidc]) + +const style = $ => Signal.link((geometry, symbolModifiers, sidc, styleRegistry) => { const styleProps = [{ id: 'style:2525c/symbol', geometry, 'symbol-code': sidc, - 'symbol-modifiers': symbolModifiers, - // TODO: selected styles + 'symbol-modifiers': symbolModifiers }] return styleProps .map(styleRegistry) .flatMap(styleFactory) -}, [feature.$definingGeometry, feature.$symbolModifiers, feature.$sidc, feature.$styleRegistry]) +}, [$.definingGeometry, $.symbolModifiers, $.sidc, $.styleRegistry]) export default { simplifyGeometry: R.identity, smoothenGeometry: R.identity, + labels: R.always([]), + labelPlacement: R.always(R.identity), + mainStyles, style } diff --git a/src/renderer/ol/style/__Polygon.js b/src/renderer/ol/style/__Polygon.js index 6a25df9e..42cab59a 100644 --- a/src/renderer/ol/style/__Polygon.js +++ b/src/renderer/ol/style/__Polygon.js @@ -1,5 +1,16 @@ +import Signal from '@syncpoint/signal' +import * as TS from '../ts' +import * as Math from '../../../shared/Math' import { style } from './__style' import { smooth } from './chaikin' +import { labels } from './polygon-styles/labels' +import styles from './polygon-styles' +import { placement } from './polygon-placement' + +const mainStyles = $ => Signal.link((geometry, sidc, resolution) => { + const context = { geometry, resolution, TS, ...Math } + return (styles[sidc] || styles.DEFAULT)(context) +}, [$.geometry, $.parameterizedSIDC, $.resolution]) const simplifyGeometry = (geometry, resolution) => geometry.getCoordinates()[0].length > 50 @@ -11,5 +22,8 @@ const smoothenGeometry = geometry => smooth(geometry) export default { simplifyGeometry, smoothenGeometry, - style: feature => feature.$smoothenedGeometry.map(style) + labels: parameterizedSIDC => labels[parameterizedSIDC] || [], + labelPlacement: placement, + mainStyles, + style: $ => $.olSmoothenedGeometry.map(style) } diff --git a/src/renderer/ol/style/__style.js b/src/renderer/ol/style/__style.js index 05f409ee..ecbcd020 100644 --- a/src/renderer/ol/style/__style.js +++ b/src/renderer/ol/style/__style.js @@ -8,6 +8,6 @@ export const style = geometry => { geometry, image: new Circle({ fill, stroke, radius: 5 }), fill, - stroke, + stroke }) } diff --git a/src/renderer/ol/style/__styles.js b/src/renderer/ol/style/__styles.js index c0eb3fdc..0502ff75 100644 --- a/src/renderer/ol/style/__styles.js +++ b/src/renderer/ol/style/__styles.js @@ -3,85 +3,88 @@ import Signal from '@syncpoint/signal' import Polygon from './__Polygon' import LineString from './__LineString' import Point from './__Point' -import { style } from './__style' -import { echelonCode, identityCode, statusCode, parameterized, MODIFIERS } from '../../symbology/2525c' -import * as Colors from './color-schemes' +import { style as defaultStyle } from './__style' +import { parameterized } from '../../symbology/2525c' import * as Geometry from '../../model/geometry' import styleRegistry from './styleRegistry' +import { styleFactory } from './styleFactory' + +import symbolModifiers from './_symbolModifers' +import colorScheme from './_colorScheme' +import schemeStyle from './_schemeStyle' +import effectiveStyle from './_effectiveStyle' +import smoothenedGeometry from './_smoothenedGeometry' +import transform from './_transform' +import labelEval from './_labelReplacements' const other = { simplifyGeometry: R.identity, smoothenGeometry: R.identity, - style: () => Signal.of(style()) + labels: R.always([]), + labelPlacement: R.always(R.identity), + mainStyles: R.always(Signal.of([])), + style: R.always(Signal.of(defaultStyle())) } -const geometryHooks = { +const HOOKS = { Polygon, LineString, Point, other } -export const $style = feature => { +export const style = feature => { const geometryType = Geometry.geometryType(feature.getGeometry()) - const hooks = geometryHooks[geometryType] ?? geometryHooks.other - - feature.$definingGeometry = feature.$feature.map(feature => feature.getGeometry()) - feature.$properties = feature.$feature.map(feature => feature.getProperties()) - feature.$modifiers = feature.$properties.map(({ sidc, ...modifiers }) => modifiers) - feature.$sidc = feature.$properties.map(R.prop('sidc')) - - feature.$symbolModifiers = Signal.link((properties) => { - return Object.entries(properties) - .filter(([key, value]) => MODIFIERS[key] && value) - .reduce((acc, [key, value]) => R.tap(acc => (acc[MODIFIERS[key]] = value), acc), {}) - }, [feature.$properties]) - - feature.$parameterizedSIDC = feature.$sidc.map(parameterized) - - feature.$colorScheme = Signal.link((globalStyle, layerStyle, featureStyle) => { - return featureStyle?.['color-scheme'] || - layerStyle?.['color-scheme'] || - globalStyle?.['color-scheme'] || - 'medium' - }, [feature.$globalStyle, feature.$layerStyle, feature.$featureStyle]) - - feature.$schemeStyle = Signal.link((sidc, colorScheme) => { - const status = statusCode(sidc) - const identity = identityCode(sidc) - const simpleIdentity = identity === 'H' || identity === 'S' - ? 'H' - : '-' + const hooks = HOOKS[geometryType] ?? HOOKS.other + const { $ } = feature - return { - 'binary-color': Colors.lineColor(colorScheme)(simpleIdentity), // black or red - 'line-color': Colors.lineColor(colorScheme)(identity), - 'fill-color': Colors.lineColor(colorScheme)(identity), - 'line-dash-array': status === 'A' ? [20, 10] : null, - 'line-halo-color': Colors.lineHaloColor(identity), - 'line-halo-dash-array': status === 'A' ? [20, 10] : null - } - }, [feature.$sidc, feature.$colorScheme]) + // definingGeometry :: ol/geom/Geometry - original feature geometry as stored in database + // properties :: { k: v } - feature properties incl. SIDC + // * modifiers :: { k: v } - feature properties excl. SIDC + // sidc :: String + // parameterizedSIDC :: String - normalized/parameterized SIDC + // symbolModifiers :: { k: v } - 2525C text modifiers + // labels :: [StyleDefinition] || [] + // colorScheme :: String - 'light' | 'medium' | 'dark' + // schemeStyle :: StyleDefinition - symbol style presets based on status and identity + // effectiveStyle :: StyleDefinition - merged style definitions + // lineSmoothing :: Boolean + // styleRegistry :: StyleDefinition -> StyleDefinition - parameterized style lookup/translation + // olSimplifiedGeometry :: ol/geom/Geometry - LineString/Polygon only, definingGeometry else + // olSmoothenedGeometry :: ol/geom/Geometry + // transform :: { read, write, pointResolution} - WGS84/UTM projection - feature.$effectiveStyle = Signal.link((globalStyle, schemeStyle, layerStyle, featureStyle) => { - if (!layerStyle['line-color']) delete layerStyle['line-color'] - if (!layerStyle['line-halo-color']) delete layerStyle['line-halo-color'] + $.definingGeometry = $.feature.map(feature => feature.getGeometry()) + $.properties = $.feature.map(feature => feature.getProperties()) + $.modifiers = $.properties.map(({ sidc, ...modifiers }) => modifiers) + $.sidc = $.properties.map(R.prop('sidc')) + $.evalSync = Signal.link(labelEval, [$.sidc, $.modifiers]) + $.parameterizedSIDC = $.sidc.map(parameterized) + $.symbolModifiers = Signal.link(symbolModifiers, [$.properties]) + $.labels = $.parameterizedSIDC.map(hooks.labels).map(xs => xs.flat()) + $.colorScheme = Signal.link(colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) + $.schemeStyle = Signal.link(schemeStyle, [$.sidc, $.colorScheme]) + $.effectiveStyle = Signal.link(effectiveStyle, [$.globalStyle, $.schemeStyle, $.layerStyle, $.featureStyle]) + $.lineSmoothing = $.effectiveStyle.map(style => style['line-smooth'] || false) + $.styleRegistry = $.effectiveStyle.map(styleRegistry) + $.olSimplifiedGeometry = Signal.link(hooks.simplifyGeometry, [$.definingGeometry, $.centerResolution]) + $.olSmoothenedGeometry = Signal.link(smoothenedGeometry(hooks), [$.olSimplifiedGeometry, $.lineSmoothing]) - return { - ...globalStyle, - ...schemeStyle, - ...layerStyle, - ...featureStyle - } - }, [feature.$globalStyle, feature.$schemeStyle, feature.$layerStyle, feature.$featureStyle]) + const [read, write, pointResolution] = transform($.olSmoothenedGeometry) + $.read = read + $.write = write - feature.$lineSmoothing = feature.$effectiveStyle.map(R.prop('line-smooth')) - feature.$styleRegistry = feature.$effectiveStyle.map(styleRegistry) - feature.$simplifiedGeometry = Signal.link(hooks.simplifyGeometry, [feature.$definingGeometry, feature.$resolution]) + $.resolution = $.centerResolution.ap(pointResolution) + $.jtsSimplifiedGeometry = $.olSimplifiedGeometry.ap(read) + $.geometry = $.olSmoothenedGeometry.ap(read) + $.placement = $.geometry.map(hooks.labelPlacement) + $.mainStyles = hooks.mainStyles($) - feature.$smoothenedGeometry = Signal.link((simplifiedGeometry, lineSmoothing) => { - return lineSmoothing ? hooks.smoothenGeometry(simplifiedGeometry) : simplifiedGeometry - }, [feature.$simplifiedGeometry, feature.$lineSmoothing]) + return Signal.link((mainStyles, write, styleRegistry) => { + return mainStyles + .map(styleRegistry) + .map(({ geometry, ...rest }) => ({ geometry: write(geometry), ...rest })) + .flatMap(styleFactory) - return hooks.style(feature) + }, [$.mainStyles, $.write, $.styleRegistry]) } diff --git a/src/renderer/ol/style/_colorScheme.js b/src/renderer/ol/style/_colorScheme.js new file mode 100644 index 00000000..3f84cdd2 --- /dev/null +++ b/src/renderer/ol/style/_colorScheme.js @@ -0,0 +1,10 @@ + +/** + * + */ +export default (globalStyle, layerStyle, featureStyle) => { + return featureStyle?.['color-scheme'] || + layerStyle?.['color-scheme'] || + globalStyle?.['color-scheme'] || + 'medium' +} diff --git a/src/renderer/ol/style/_effectiveStyle.js b/src/renderer/ol/style/_effectiveStyle.js new file mode 100644 index 00000000..6df90970 --- /dev/null +++ b/src/renderer/ol/style/_effectiveStyle.js @@ -0,0 +1,14 @@ + +/** + * + */ +export default (globalStyle, schemeStyle, layerStyle, featureStyle) => { + if (!layerStyle['line-color']) delete layerStyle['line-color'] + if (!layerStyle['line-halo-color']) delete layerStyle['line-halo-color'] + return { + ...globalStyle, + ...schemeStyle, + ...layerStyle, + ...featureStyle + } +} diff --git a/src/renderer/ol/style/_labelReplacements.js b/src/renderer/ol/style/_labelReplacements.js new file mode 100644 index 00000000..f964117b --- /dev/null +++ b/src/renderer/ol/style/_labelReplacements.js @@ -0,0 +1,9 @@ +import { echelonCode } from '../../symbology/2525c' +import { echelons } from './echelon' +import { evalSync } from './labels' + +export default (sidc, modifiers) => { + const sizeCode = echelonCode(sidc) + const echelonText = (sizeCode === '*' || sizeCode === '-') ? '' : echelons[sizeCode]?.text + return evalSync({ modifiers, echelon: echelonText }) +} diff --git a/src/renderer/ol/style/_schemeStyle.js b/src/renderer/ol/style/_schemeStyle.js new file mode 100644 index 00000000..9a2afbd9 --- /dev/null +++ b/src/renderer/ol/style/_schemeStyle.js @@ -0,0 +1,22 @@ +import * as Colors from './color-schemes' +import { identityCode, statusCode } from '../../symbology/2525c' + +/** + * + */ +export default (sidc, colorScheme) => { + const status = statusCode(sidc) + const identity = identityCode(sidc) + const simpleIdentity = identity === 'H' || identity === 'S' + ? 'H' + : '-' + + return { + 'binary-color': Colors.lineColor(colorScheme)(simpleIdentity), // black or red + 'line-color': Colors.lineColor(colorScheme)(identity), + 'fill-color': Colors.lineColor(colorScheme)(identity), + 'line-dash-array': status === 'A' ? [20, 10] : null, + 'line-halo-color': Colors.lineHaloColor(identity), + 'line-halo-dash-array': status === 'A' ? [20, 10] : null + } +} diff --git a/src/renderer/ol/style/_smoothenedGeometry.js b/src/renderer/ol/style/_smoothenedGeometry.js new file mode 100644 index 00000000..5186e6b8 --- /dev/null +++ b/src/renderer/ol/style/_smoothenedGeometry.js @@ -0,0 +1,8 @@ + +/** + * + */ +export default hooks => (simplifiedGeometry, lineSmoothing) => + lineSmoothing + ? hooks.smoothenGeometry(simplifiedGeometry) + : simplifiedGeometry diff --git a/src/renderer/ol/style/_symbolModifers.js b/src/renderer/ol/style/_symbolModifers.js new file mode 100644 index 00000000..b2f97ff9 --- /dev/null +++ b/src/renderer/ol/style/_symbolModifers.js @@ -0,0 +1,11 @@ +import * as R from 'ramda' +import { MODIFIERS } from '../../symbology/2525c' + +/** + * + */ +export default (properties) => { + return Object.entries(properties) + .filter(([key, value]) => MODIFIERS[key] && value) + .reduce((acc, [key, value]) => R.tap(acc => (acc[MODIFIERS[key]] = value), acc), {}) +} diff --git a/src/renderer/ol/style/_transform.js b/src/renderer/ol/style/_transform.js new file mode 100644 index 00000000..874297fc --- /dev/null +++ b/src/renderer/ol/style/_transform.js @@ -0,0 +1,8 @@ +import * as R from 'ramda' +import { transform } from '../../model/geometry' +import { destructure } from '../../../shared/signal' + +export default R.compose( + destructure(['read', 'write', 'pointResolution']), + R.map(transform) +) diff --git a/src/renderer/ol/style/linestring.js b/src/renderer/ol/style/linestring.js index 3597d6bc..b37c75d3 100644 --- a/src/renderer/ol/style/linestring.js +++ b/src/renderer/ol/style/linestring.js @@ -34,9 +34,3 @@ export default [ shared.styles, shared.style ] - - -// ==> label specifications and placement - - - diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index 62f253bf..fbb42710 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -7,7 +7,7 @@ import Signal from '@syncpoint/signal' import Emitter from '../../shared/emitter' import { flatten, select, split } from '../../shared/signal' import * as ID from '../ids' -import { $style } from '../ol/style/__styles' +import { style } from '../ol/style/__styles' const format = new GeoJSON({ @@ -40,9 +40,6 @@ const isGeometry = value => { } } -const apply = options => feature => feature && feature.apply(options, true) - - // Batch operations order: // 0 - (del, style+) // 1 - (del, feature) @@ -56,11 +53,6 @@ const ord = R.cond([ [R.T, R.always(4)] ]) -const assign = (acc, [key, value]) => { - acc[key] = value - return acc -} - const push = (acc, [key, value]) => { acc.push({ type: 'put', key, value }) return acc @@ -84,47 +76,49 @@ export function FeatureStore (store, selection, emitter) { emitter.addEventListener = (type, handler) => emitter.on(type, handler) emitter.removeEventListener = (type, handler) => emitter.off(type, handler) - const $operations = R.compose( + const operations = R.compose( flatten, R.map(R.sort((a, b) => ord(a) - ord(b))), R.map(R.prop('operations')) )(Signal.fromListeners(['batch'], store)) const [ - $globalStyle, - $featureStyle, - $layerStyle, - $feature + globalStyle, + featureStyle, + layerStyle, + feature ] = select([ R.propEq(ID.defaultStyleId, 'key'), R.compose(ID.isFeatureStyleId, R.prop('key')), R.compose(ID.isLayerStyleId, R.prop('key')), R.compose(isCandidateId, R.prop('key')) - ], $operations) + ], operations) - // $globalStyle.on(({ value }) => Object.values(this.features).forEach(apply({ globalStyle: value }))) + globalStyle.on(({ value }) => { + Object.values(this.features).forEach(feature => feature.$.globalStyle(value)) + }) - $featureStyle.on(({ type, key, value }) => { + featureStyle.on(({ type, key, value }) => { const feature = this.features[ID.featureId(key)] - if (feature) feature.$featureStyle(type === 'put' ? value : {}) + if (feature) feature.$.featureStyle(type === 'put' ? value : {}) }) - $layerStyle.on(({ type, key, value }) => { + layerStyle.on(({ type, key, value }) => { const layerId = ID.layerId(key) Object.entries(this.features) - .filter(([key, ]) => ID.layerId(key) === layerId) - .forEach(([, feature]) => feature.$layerStyle(type === 'put' ? value : {})) + .filter(([key]) => ID.layerId(key) === layerId) + .forEach(([, feature]) => feature.$.layerStyle(type === 'put' ? value : {})) }) const [ - $featureRemoval, - $featureUpdate, - $featureAddition + featureRemoval, + featureUpdate, + featureAddition ] = select([ R.propEq('del', 'type'), ({ key }) => this.features[key], R.T - ], $feature) + ], feature) const isValid = feature => feature?.type === 'Feature' && feature.geometry @@ -135,13 +129,13 @@ export function FeatureStore (store, selection, emitter) { : rest } - $featureRemoval.on(({ key }) => { + featureRemoval.on(({ key }) => { const features = [this.features[key]] delete this.features[key] this.emit('removefeatures', ({ features })) }) - $featureAddition + featureAddition .map(({ key, value }) => ({ id: key, ...value })) .map(readFeature) // .filter(feature => Geometry.geometryType(feature) === 'Polygon') @@ -150,7 +144,7 @@ export function FeatureStore (store, selection, emitter) { .map(this.wrapFeature.bind(this)) .on(feature => this.features[feature.getId()] = feature) - $featureUpdate.on(({ key, value }) => { + featureUpdate.on(({ key, value }) => { const properties = isGeometry(value) ? { geometry: readGeometry(value) } : trim(readFeature(value).getProperties()) @@ -191,9 +185,9 @@ export function FeatureStore (store, selection, emitter) { // }) // }) - const $resolution = Signal.fromListeners(['view/resolution'], emitter) - $resolution.on(({ resolution }) => { - Object.values(this.features).forEach(feature => feature.$resolution(resolution)) + const $centerResolution = Signal.fromListeners(['view/resolution'], emitter) + $centerResolution.on(({ resolution }) => { + Object.values(this.features).forEach(feature => feature.$.centerResolution(resolution)) }) } @@ -204,6 +198,8 @@ util.inherits(FeatureStore, Emitter) */ FeatureStore.prototype.bootstrap = async function () { + // TODO: pre-load and store global style + const reduce = async (prefix, fn, acc) => { const db = this.store.db const it = db.iterator({ gte: `${prefix}`, lte: `${prefix}\xff` }) @@ -245,15 +241,15 @@ FeatureStore.prototype.wrapFeature = function (feature) { const featureId = feature.getId() const layerId = ID.layerId(featureId) - feature.$feature = Signal.of(feature) - feature.$globalStyle = Signal.of({}) - feature.$layerStyle = Signal.of({}) - feature.$featureStyle = Signal.of({}) - feature.$resolution = Signal.of() - - feature.$style = $style(feature) - feature.$style.on(feature.setStyle.bind(feature)) + feature.$ = { + feature: Signal.of(feature), + globalStyle: Signal.of(), + layerStyle: Signal.of({}), + featureStyle: Signal.of({}), + centerResolution: Signal.of() + } + style(feature).on(feature.setStyle.bind(feature)) // Use dedicated function to update feature coordinates from within // modify interaction. Such internal changes must not trigger ModifyEvent. diff --git a/src/shared/signal.js b/src/shared/signal.js index e04f9a65..f9cc6456 100644 --- a/src/shared/signal.js +++ b/src/shared/signal.js @@ -36,4 +36,16 @@ export const split = R.curry((fns, signal) => { const outputs = fns.map(() => Signal.of()) signal.on(value => fns.forEach((fn, i) => outputs[i](fn(value)))) return outputs -}) \ No newline at end of file +}) + +/** + * destructure :: Signal S => [String] -> S { k: v } -> [S Any] + * + * Split input object signal into ordered list of value signals + * based on entry keys. + */ +export const destructure = R.curry((keys, signal) => { + const outputs = keys.map(() => Signal.of()) + signal.on(object => keys.forEach((key, i) => outputs[i](object[key]))) + return outputs +}) From 3a7922895a09c05c24fddc201ca3bca3c945a04f Mon Sep 17 00:00:00 2001 From: dehmer Date: Sat, 29 Jun 2024 18:46:48 +0200 Subject: [PATCH 16/73] working on labels (WIP). --- src/renderer/ol/style/__styles.js | 16 ++++++++++------ .../{_labelReplacements.js => _evalSync.js} | 0 src/renderer/ol/style/_labelStyles.js | 6 ++++++ src/renderer/store/FeatureStore.js | 6 ++++-- 4 files changed, 20 insertions(+), 8 deletions(-) rename src/renderer/ol/style/{_labelReplacements.js => _evalSync.js} (100%) create mode 100644 src/renderer/ol/style/_labelStyles.js diff --git a/src/renderer/ol/style/__styles.js b/src/renderer/ol/style/__styles.js index 0502ff75..1d5ee8bb 100644 --- a/src/renderer/ol/style/__styles.js +++ b/src/renderer/ol/style/__styles.js @@ -15,7 +15,8 @@ import schemeStyle from './_schemeStyle' import effectiveStyle from './_effectiveStyle' import smoothenedGeometry from './_smoothenedGeometry' import transform from './_transform' -import labelEval from './_labelReplacements' +import evalSync from './_evalSync' +import labelStyles from './_labelStyles' const other = { simplifyGeometry: R.identity, @@ -58,7 +59,7 @@ export const style = feature => { $.properties = $.feature.map(feature => feature.getProperties()) $.modifiers = $.properties.map(({ sidc, ...modifiers }) => modifiers) $.sidc = $.properties.map(R.prop('sidc')) - $.evalSync = Signal.link(labelEval, [$.sidc, $.modifiers]) + $.evalSync = Signal.link(evalSync, [$.sidc, $.modifiers]) $.parameterizedSIDC = $.sidc.map(parameterized) $.symbolModifiers = Signal.link(symbolModifiers, [$.properties]) $.labels = $.parameterizedSIDC.map(hooks.labels).map(xs => xs.flat()) @@ -77,14 +78,17 @@ export const style = feature => { $.resolution = $.centerResolution.ap(pointResolution) $.jtsSimplifiedGeometry = $.olSimplifiedGeometry.ap(read) $.geometry = $.olSmoothenedGeometry.ap(read) - $.placement = $.geometry.map(hooks.labelPlacement) + $.labelPlacement = $.geometry.map(hooks.labelPlacement) $.mainStyles = hooks.mainStyles($) + $.labelStyles = Signal.link(labelStyles, [$.labels, $.labelPlacement]) + $.allStyles = Signal.link((mainStyles, labelStyles, ) => mainStyles.concat(labelStyles), [$.mainStyles, $.labelStyles]) - return Signal.link((mainStyles, write, styleRegistry) => { - return mainStyles + return Signal.link((styles, evalSync, write, styleRegistry) => { + return styles .map(styleRegistry) + .flatMap(evalSync) .map(({ geometry, ...rest }) => ({ geometry: write(geometry), ...rest })) .flatMap(styleFactory) - }, [$.mainStyles, $.write, $.styleRegistry]) + }, [$.allStyles, $.evalSync, $.write, $.styleRegistry]) } diff --git a/src/renderer/ol/style/_labelReplacements.js b/src/renderer/ol/style/_evalSync.js similarity index 100% rename from src/renderer/ol/style/_labelReplacements.js rename to src/renderer/ol/style/_evalSync.js diff --git a/src/renderer/ol/style/_labelStyles.js b/src/renderer/ol/style/_labelStyles.js new file mode 100644 index 00000000..1c4b960a --- /dev/null +++ b/src/renderer/ol/style/_labelStyles.js @@ -0,0 +1,6 @@ +import * as R from 'ramda' + +export default (labels, labelPlacement) => + labels + .map(R.tryCatch(labelPlacement, err => { console.warn(err); return undefined })) + .filter(Boolean) diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index fbb42710..b4b5a994 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -198,7 +198,8 @@ util.inherits(FeatureStore, Emitter) */ FeatureStore.prototype.bootstrap = async function () { - // TODO: pre-load and store global style + // pre-load and store global style + this.globalStyle = await this.store.value(ID.defaultStyleId) const reduce = async (prefix, fn, acc) => { const db = this.store.db @@ -243,12 +244,13 @@ FeatureStore.prototype.wrapFeature = function (feature) { feature.$ = { feature: Signal.of(feature), - globalStyle: Signal.of(), + globalStyle: Signal.of(this.globalStyle), layerStyle: Signal.of({}), featureStyle: Signal.of({}), centerResolution: Signal.of() } + feature.setStyle([]) // no initial style style(feature).on(feature.setStyle.bind(feature)) // Use dedicated function to update feature coordinates from within From 7f7da1390efbc57666b0f47b922d04ef494e5d02 Mon Sep 17 00:00:00 2001 From: dehmer Date: Sun, 30 Jun 2024 14:57:41 +0200 Subject: [PATCH 17/73] playing with signals (WIP). --- src/renderer/model/Sources.js | 4 ++-- src/renderer/ol/style/__styles.js | 9 ++++++--- src/renderer/store/FeatureStore.js | 27 +++++++++++++++++++++------ src/shared/signal.js | 7 +++++++ 4 files changed, 36 insertions(+), 11 deletions(-) diff --git a/src/renderer/model/Sources.js b/src/renderer/model/Sources.js index 4c806f36..66be41df 100644 --- a/src/renderer/model/Sources.js +++ b/src/renderer/model/Sources.js @@ -14,8 +14,8 @@ export const featureSource = (featureStore, scope) => { ? ID.isId(scope)(feature.getId()) : false - const features = Object.values(featureStore.features).filter(matchesScope) - const source = new VectorSource({ features }) + // const features = Object.values(featureStore.features).filter(matchesScope) + const source = new VectorSource({ features: [] }) featureStore.on('addfeatures', ({ features }) => { source.addFeatures(features.filter(matchesScope)) diff --git a/src/renderer/ol/style/__styles.js b/src/renderer/ol/style/__styles.js index 1d5ee8bb..f97fe121 100644 --- a/src/renderer/ol/style/__styles.js +++ b/src/renderer/ol/style/__styles.js @@ -57,10 +57,10 @@ export const style = feature => { $.definingGeometry = $.feature.map(feature => feature.getGeometry()) $.properties = $.feature.map(feature => feature.getProperties()) - $.modifiers = $.properties.map(({ sidc, ...modifiers }) => modifiers) + $.modifiers = $.properties.map(({ sidc, geometry, ...modifiers }) => modifiers) $.sidc = $.properties.map(R.prop('sidc')) - $.evalSync = Signal.link(evalSync, [$.sidc, $.modifiers]) $.parameterizedSIDC = $.sidc.map(parameterized) + $.evalSync = Signal.link(evalSync, [$.sidc, $.modifiers]) $.symbolModifiers = Signal.link(symbolModifiers, [$.properties]) $.labels = $.parameterizedSIDC.map(hooks.labels).map(xs => xs.flat()) $.colorScheme = Signal.link(colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) @@ -71,6 +71,7 @@ export const style = feature => { $.olSimplifiedGeometry = Signal.link(hooks.simplifyGeometry, [$.definingGeometry, $.centerResolution]) $.olSmoothenedGeometry = Signal.link(smoothenedGeometry(hooks), [$.olSimplifiedGeometry, $.lineSmoothing]) + const [read, write, pointResolution] = transform($.olSmoothenedGeometry) $.read = read $.write = write @@ -81,7 +82,9 @@ export const style = feature => { $.labelPlacement = $.geometry.map(hooks.labelPlacement) $.mainStyles = hooks.mainStyles($) $.labelStyles = Signal.link(labelStyles, [$.labels, $.labelPlacement]) - $.allStyles = Signal.link((mainStyles, labelStyles, ) => mainStyles.concat(labelStyles), [$.mainStyles, $.labelStyles]) + $.allStyles = Signal.link((mainStyles, labelStyles) => mainStyles.concat(labelStyles), [$.mainStyles, $.labelStyles]) + + return Signal.link((styles, evalSync, write, styleRegistry) => { return styles diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index b4b5a994..3136bd28 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -5,7 +5,7 @@ import GeoJSON from 'ol/format/GeoJSON' import * as Extent from 'ol/extent' import Signal from '@syncpoint/signal' import Emitter from '../../shared/emitter' -import { flatten, select, split } from '../../shared/signal' +import { flatten, select, split, once } from '../../shared/signal' import * as ID from '../ids' import { style } from '../ol/style/__styles' @@ -185,8 +185,9 @@ export function FeatureStore (store, selection, emitter) { // }) // }) - const $centerResolution = Signal.fromListeners(['view/resolution'], emitter) - $centerResolution.on(({ resolution }) => { + const centerResolution = Signal.fromListeners(['view/resolution'], emitter) + centerResolution.on(({ resolution }) => { + this.resolution = resolution Object.values(this.features).forEach(feature => feature.$.centerResolution(resolution)) }) } @@ -242,16 +243,30 @@ FeatureStore.prototype.wrapFeature = function (feature) { const featureId = feature.getId() const layerId = ID.layerId(featureId) + // console.log('higherFormation', feature.getProperties()) + + const t = feature.getProperties().t + if (t === '1 EST') console.log(featureId) + feature.$ = { feature: Signal.of(feature), globalStyle: Signal.of(this.globalStyle), layerStyle: Signal.of({}), featureStyle: Signal.of({}), - centerResolution: Signal.of() + centerResolution: Signal.of(this.resolution) } - feature.setStyle([]) // no initial style - style(feature).on(feature.setStyle.bind(feature)) + // feature.setStyle([]) // no initial style + const $style = style(feature) + once(() => this.emit('addfeatures', { features: [feature] }), $style) + $style.on(style => { + if (featureId === 'feature:2ea57605-8e1e-4c46-9ec1-8e7b162af538/7feeea69-074a-4fb3-8515-4cfd74b717d6') { + console.log(style) + } + feature.setStyle(style) + // feature.setStyle.bind(feature) + + }) // Use dedicated function to update feature coordinates from within // modify interaction. Such internal changes must not trigger ModifyEvent. diff --git a/src/shared/signal.js b/src/shared/signal.js index f9cc6456..e914309f 100644 --- a/src/shared/signal.js +++ b/src/shared/signal.js @@ -49,3 +49,10 @@ export const destructure = R.curry((keys, signal) => { signal.on(object => keys.forEach((key, i) => outputs[i](object[key]))) return outputs }) + +export const once = (fn, signal) => { + const dispose = signal.on(value => { + setImmediate(() => dispose()) + fn(value) + }) +} From 436e2bca0e49cb9e75e3a1dba1a5ccddc2aeb694 Mon Sep 17 00:00:00 2001 From: dehmer Date: Sun, 30 Jun 2024 21:45:35 +0200 Subject: [PATCH 18/73] start over... --- src/renderer/model/Sources.js | 3 +- src/renderer/ol/style/__LineString.js | 37 ---- src/renderer/ol/style/__Point.js | 31 --- src/renderer/ol/style/__Polygon.js | 29 --- src/renderer/ol/style/__style.js | 13 -- src/renderer/ol/style/__styles.js | 97 --------- src/renderer/ol/style/_colorScheme.js | 10 - src/renderer/ol/style/_effectiveStyle.js | 14 -- src/renderer/ol/style/_evalSync.js | 9 - src/renderer/ol/style/_labelStyles.js | 6 - src/renderer/ol/style/_schemeStyle.js | 22 -- src/renderer/ol/style/_smoothenedGeometry.js | 8 - src/renderer/ol/style/_symbolModifers.js | 11 - src/renderer/ol/style/_transform.js | 8 - src/renderer/ol/style/chaikin.js | 48 ----- src/renderer/ol/style/color-schemes.js | 42 ---- src/renderer/ol/style/corridor.js | 55 ----- src/renderer/ol/style/crosshair.js | 20 -- src/renderer/ol/style/echelon.js | 53 ----- src/renderer/ol/style/labels.js | 116 ----------- src/renderer/ol/style/linestring-placement.js | 50 ----- src/renderer/ol/style/linestring.js | 36 ---- src/renderer/ol/style/multipoint.js | 67 ------- src/renderer/ol/style/patterns.js | 90 --------- src/renderer/ol/style/point.js | 40 ---- src/renderer/ol/style/polygon-placement.js | 57 ------ src/renderer/ol/style/polygon.js | 45 ----- src/renderer/ol/style/rules.js | 56 ------ src/renderer/ol/style/shared.js | 188 ------------------ src/renderer/ol/style/styles.js | 10 + src/renderer/store/FeatureStore.js | 17 +- 31 files changed, 15 insertions(+), 1273 deletions(-) delete mode 100644 src/renderer/ol/style/__LineString.js delete mode 100644 src/renderer/ol/style/__Point.js delete mode 100644 src/renderer/ol/style/__Polygon.js delete mode 100644 src/renderer/ol/style/__style.js delete mode 100644 src/renderer/ol/style/__styles.js delete mode 100644 src/renderer/ol/style/_colorScheme.js delete mode 100644 src/renderer/ol/style/_effectiveStyle.js delete mode 100644 src/renderer/ol/style/_evalSync.js delete mode 100644 src/renderer/ol/style/_labelStyles.js delete mode 100644 src/renderer/ol/style/_schemeStyle.js delete mode 100644 src/renderer/ol/style/_smoothenedGeometry.js delete mode 100644 src/renderer/ol/style/_symbolModifers.js delete mode 100644 src/renderer/ol/style/_transform.js delete mode 100644 src/renderer/ol/style/chaikin.js delete mode 100644 src/renderer/ol/style/color-schemes.js delete mode 100644 src/renderer/ol/style/corridor.js delete mode 100644 src/renderer/ol/style/crosshair.js delete mode 100644 src/renderer/ol/style/echelon.js delete mode 100644 src/renderer/ol/style/labels.js delete mode 100644 src/renderer/ol/style/linestring-placement.js delete mode 100644 src/renderer/ol/style/linestring.js delete mode 100644 src/renderer/ol/style/multipoint.js delete mode 100644 src/renderer/ol/style/patterns.js delete mode 100644 src/renderer/ol/style/point.js delete mode 100644 src/renderer/ol/style/polygon-placement.js delete mode 100644 src/renderer/ol/style/polygon.js delete mode 100644 src/renderer/ol/style/rules.js delete mode 100644 src/renderer/ol/style/shared.js create mode 100644 src/renderer/ol/style/styles.js diff --git a/src/renderer/model/Sources.js b/src/renderer/model/Sources.js index 66be41df..6b078f4c 100644 --- a/src/renderer/model/Sources.js +++ b/src/renderer/model/Sources.js @@ -14,9 +14,10 @@ export const featureSource = (featureStore, scope) => { ? ID.isId(scope)(feature.getId()) : false - // const features = Object.values(featureStore.features).filter(matchesScope) const source = new VectorSource({ features: [] }) + console.log('[featureSource] registering listeners...') + featureStore.on('addfeatures', ({ features }) => { source.addFeatures(features.filter(matchesScope)) }) diff --git a/src/renderer/ol/style/__LineString.js b/src/renderer/ol/style/__LineString.js deleted file mode 100644 index f750bde1..00000000 --- a/src/renderer/ol/style/__LineString.js +++ /dev/null @@ -1,37 +0,0 @@ -import Signal from '@syncpoint/signal' -import * as TS from '../ts' -import * as Math from '../../../shared/Math' -import { smooth } from './chaikin' -import { labels } from './linestring-styles/labels' -import styles from './linestring-styles' -import { styleFactory } from './styleFactory' -import { placement } from './linestring-placement' - -const simplifyGeometry = (geometry, resolution) => - geometry.getCoordinates().length > 50 - ? geometry.simplify(resolution) - : geometry - -const smoothenGeometry = geometry => smooth(geometry) - -const mainStyles = $ => Signal.link((geometry, sidc, resolution) => { - const context = { geometry, resolution, TS, ...Math } - return (styles[sidc] || styles.DEFAULT)(context) -}, [$.geometry, $.parameterizedSIDC, $.resolution]) - -const style = $ => Signal.link((geometry, sidc, write, resolution, styleRegistry) => { - const context = { geometry, resolution, TS, ...Math } - return (styles[sidc] || styles.DEFAULT)(context) - .map(({ geometry, ...rest }) => ({ geometry: write(geometry), ...rest })) - .map(styleRegistry) - .flatMap(styleFactory) -}, [$.geometry, $.parameterizedSIDC, $.write, $.resolution, $.styleRegistry]) - -export default { - simplifyGeometry, - smoothenGeometry, - labels: parameterizedSIDC => labels[parameterizedSIDC] || [], - labelPlacement: placement, - mainStyles, - style -} diff --git a/src/renderer/ol/style/__Point.js b/src/renderer/ol/style/__Point.js deleted file mode 100644 index 07761f38..00000000 --- a/src/renderer/ol/style/__Point.js +++ /dev/null @@ -1,31 +0,0 @@ -import * as R from 'ramda' -import Signal from '@syncpoint/signal' -import { styleFactory } from './styleFactory' - -const mainStyles = $ => Signal.link((geometry, symbolModifiers, sidc) => [{ - id: 'style:2525c/symbol', - geometry, - 'symbol-code': sidc, - 'symbol-modifiers': symbolModifiers -}], [$.geometry, $.symbolModifiers, $.sidc]) - -const style = $ => Signal.link((geometry, symbolModifiers, sidc, styleRegistry) => { - const styleProps = [{ - id: 'style:2525c/symbol', - geometry, - 'symbol-code': sidc, - 'symbol-modifiers': symbolModifiers - }] - return styleProps - .map(styleRegistry) - .flatMap(styleFactory) -}, [$.definingGeometry, $.symbolModifiers, $.sidc, $.styleRegistry]) - -export default { - simplifyGeometry: R.identity, - smoothenGeometry: R.identity, - labels: R.always([]), - labelPlacement: R.always(R.identity), - mainStyles, - style -} diff --git a/src/renderer/ol/style/__Polygon.js b/src/renderer/ol/style/__Polygon.js deleted file mode 100644 index 42cab59a..00000000 --- a/src/renderer/ol/style/__Polygon.js +++ /dev/null @@ -1,29 +0,0 @@ -import Signal from '@syncpoint/signal' -import * as TS from '../ts' -import * as Math from '../../../shared/Math' -import { style } from './__style' -import { smooth } from './chaikin' -import { labels } from './polygon-styles/labels' -import styles from './polygon-styles' -import { placement } from './polygon-placement' - -const mainStyles = $ => Signal.link((geometry, sidc, resolution) => { - const context = { geometry, resolution, TS, ...Math } - return (styles[sidc] || styles.DEFAULT)(context) -}, [$.geometry, $.parameterizedSIDC, $.resolution]) - -const simplifyGeometry = (geometry, resolution) => - geometry.getCoordinates()[0].length > 50 - ? geometry.simplify(resolution) - : geometry - -const smoothenGeometry = geometry => smooth(geometry) - -export default { - simplifyGeometry, - smoothenGeometry, - labels: parameterizedSIDC => labels[parameterizedSIDC] || [], - labelPlacement: placement, - mainStyles, - style: $ => $.olSmoothenedGeometry.map(style) -} diff --git a/src/renderer/ol/style/__style.js b/src/renderer/ol/style/__style.js deleted file mode 100644 index ecbcd020..00000000 --- a/src/renderer/ol/style/__style.js +++ /dev/null @@ -1,13 +0,0 @@ -import { Circle, Fill, Stroke, Style } from 'ol/style' - -export const style = geometry => { - const fill = new Fill({ color: 'rgba(255,255,255,0.3)' }) - const strokeColor = '#888' - const stroke = new Stroke({ color: strokeColor, width: 1.25 }) - return new Style({ - geometry, - image: new Circle({ fill, stroke, radius: 5 }), - fill, - stroke - }) -} diff --git a/src/renderer/ol/style/__styles.js b/src/renderer/ol/style/__styles.js deleted file mode 100644 index f97fe121..00000000 --- a/src/renderer/ol/style/__styles.js +++ /dev/null @@ -1,97 +0,0 @@ -import * as R from 'ramda' -import Signal from '@syncpoint/signal' -import Polygon from './__Polygon' -import LineString from './__LineString' -import Point from './__Point' -import { style as defaultStyle } from './__style' -import { parameterized } from '../../symbology/2525c' -import * as Geometry from '../../model/geometry' -import styleRegistry from './styleRegistry' -import { styleFactory } from './styleFactory' - -import symbolModifiers from './_symbolModifers' -import colorScheme from './_colorScheme' -import schemeStyle from './_schemeStyle' -import effectiveStyle from './_effectiveStyle' -import smoothenedGeometry from './_smoothenedGeometry' -import transform from './_transform' -import evalSync from './_evalSync' -import labelStyles from './_labelStyles' - -const other = { - simplifyGeometry: R.identity, - smoothenGeometry: R.identity, - labels: R.always([]), - labelPlacement: R.always(R.identity), - mainStyles: R.always(Signal.of([])), - style: R.always(Signal.of(defaultStyle())) -} - -const HOOKS = { - Polygon, - LineString, - Point, - other -} - -export const style = feature => { - const geometryType = Geometry.geometryType(feature.getGeometry()) - const hooks = HOOKS[geometryType] ?? HOOKS.other - const { $ } = feature - - // definingGeometry :: ol/geom/Geometry - original feature geometry as stored in database - // properties :: { k: v } - feature properties incl. SIDC - // * modifiers :: { k: v } - feature properties excl. SIDC - // sidc :: String - // parameterizedSIDC :: String - normalized/parameterized SIDC - // symbolModifiers :: { k: v } - 2525C text modifiers - // labels :: [StyleDefinition] || [] - // colorScheme :: String - 'light' | 'medium' | 'dark' - // schemeStyle :: StyleDefinition - symbol style presets based on status and identity - // effectiveStyle :: StyleDefinition - merged style definitions - // lineSmoothing :: Boolean - // styleRegistry :: StyleDefinition -> StyleDefinition - parameterized style lookup/translation - // olSimplifiedGeometry :: ol/geom/Geometry - LineString/Polygon only, definingGeometry else - // olSmoothenedGeometry :: ol/geom/Geometry - // transform :: { read, write, pointResolution} - WGS84/UTM projection - - $.definingGeometry = $.feature.map(feature => feature.getGeometry()) - $.properties = $.feature.map(feature => feature.getProperties()) - $.modifiers = $.properties.map(({ sidc, geometry, ...modifiers }) => modifiers) - $.sidc = $.properties.map(R.prop('sidc')) - $.parameterizedSIDC = $.sidc.map(parameterized) - $.evalSync = Signal.link(evalSync, [$.sidc, $.modifiers]) - $.symbolModifiers = Signal.link(symbolModifiers, [$.properties]) - $.labels = $.parameterizedSIDC.map(hooks.labels).map(xs => xs.flat()) - $.colorScheme = Signal.link(colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) - $.schemeStyle = Signal.link(schemeStyle, [$.sidc, $.colorScheme]) - $.effectiveStyle = Signal.link(effectiveStyle, [$.globalStyle, $.schemeStyle, $.layerStyle, $.featureStyle]) - $.lineSmoothing = $.effectiveStyle.map(style => style['line-smooth'] || false) - $.styleRegistry = $.effectiveStyle.map(styleRegistry) - $.olSimplifiedGeometry = Signal.link(hooks.simplifyGeometry, [$.definingGeometry, $.centerResolution]) - $.olSmoothenedGeometry = Signal.link(smoothenedGeometry(hooks), [$.olSimplifiedGeometry, $.lineSmoothing]) - - - const [read, write, pointResolution] = transform($.olSmoothenedGeometry) - $.read = read - $.write = write - - $.resolution = $.centerResolution.ap(pointResolution) - $.jtsSimplifiedGeometry = $.olSimplifiedGeometry.ap(read) - $.geometry = $.olSmoothenedGeometry.ap(read) - $.labelPlacement = $.geometry.map(hooks.labelPlacement) - $.mainStyles = hooks.mainStyles($) - $.labelStyles = Signal.link(labelStyles, [$.labels, $.labelPlacement]) - $.allStyles = Signal.link((mainStyles, labelStyles) => mainStyles.concat(labelStyles), [$.mainStyles, $.labelStyles]) - - - - return Signal.link((styles, evalSync, write, styleRegistry) => { - return styles - .map(styleRegistry) - .flatMap(evalSync) - .map(({ geometry, ...rest }) => ({ geometry: write(geometry), ...rest })) - .flatMap(styleFactory) - - }, [$.allStyles, $.evalSync, $.write, $.styleRegistry]) -} diff --git a/src/renderer/ol/style/_colorScheme.js b/src/renderer/ol/style/_colorScheme.js deleted file mode 100644 index 3f84cdd2..00000000 --- a/src/renderer/ol/style/_colorScheme.js +++ /dev/null @@ -1,10 +0,0 @@ - -/** - * - */ -export default (globalStyle, layerStyle, featureStyle) => { - return featureStyle?.['color-scheme'] || - layerStyle?.['color-scheme'] || - globalStyle?.['color-scheme'] || - 'medium' -} diff --git a/src/renderer/ol/style/_effectiveStyle.js b/src/renderer/ol/style/_effectiveStyle.js deleted file mode 100644 index 6df90970..00000000 --- a/src/renderer/ol/style/_effectiveStyle.js +++ /dev/null @@ -1,14 +0,0 @@ - -/** - * - */ -export default (globalStyle, schemeStyle, layerStyle, featureStyle) => { - if (!layerStyle['line-color']) delete layerStyle['line-color'] - if (!layerStyle['line-halo-color']) delete layerStyle['line-halo-color'] - return { - ...globalStyle, - ...schemeStyle, - ...layerStyle, - ...featureStyle - } -} diff --git a/src/renderer/ol/style/_evalSync.js b/src/renderer/ol/style/_evalSync.js deleted file mode 100644 index f964117b..00000000 --- a/src/renderer/ol/style/_evalSync.js +++ /dev/null @@ -1,9 +0,0 @@ -import { echelonCode } from '../../symbology/2525c' -import { echelons } from './echelon' -import { evalSync } from './labels' - -export default (sidc, modifiers) => { - const sizeCode = echelonCode(sidc) - const echelonText = (sizeCode === '*' || sizeCode === '-') ? '' : echelons[sizeCode]?.text - return evalSync({ modifiers, echelon: echelonText }) -} diff --git a/src/renderer/ol/style/_labelStyles.js b/src/renderer/ol/style/_labelStyles.js deleted file mode 100644 index 1c4b960a..00000000 --- a/src/renderer/ol/style/_labelStyles.js +++ /dev/null @@ -1,6 +0,0 @@ -import * as R from 'ramda' - -export default (labels, labelPlacement) => - labels - .map(R.tryCatch(labelPlacement, err => { console.warn(err); return undefined })) - .filter(Boolean) diff --git a/src/renderer/ol/style/_schemeStyle.js b/src/renderer/ol/style/_schemeStyle.js deleted file mode 100644 index 9a2afbd9..00000000 --- a/src/renderer/ol/style/_schemeStyle.js +++ /dev/null @@ -1,22 +0,0 @@ -import * as Colors from './color-schemes' -import { identityCode, statusCode } from '../../symbology/2525c' - -/** - * - */ -export default (sidc, colorScheme) => { - const status = statusCode(sidc) - const identity = identityCode(sidc) - const simpleIdentity = identity === 'H' || identity === 'S' - ? 'H' - : '-' - - return { - 'binary-color': Colors.lineColor(colorScheme)(simpleIdentity), // black or red - 'line-color': Colors.lineColor(colorScheme)(identity), - 'fill-color': Colors.lineColor(colorScheme)(identity), - 'line-dash-array': status === 'A' ? [20, 10] : null, - 'line-halo-color': Colors.lineHaloColor(identity), - 'line-halo-dash-array': status === 'A' ? [20, 10] : null - } -} diff --git a/src/renderer/ol/style/_smoothenedGeometry.js b/src/renderer/ol/style/_smoothenedGeometry.js deleted file mode 100644 index 5186e6b8..00000000 --- a/src/renderer/ol/style/_smoothenedGeometry.js +++ /dev/null @@ -1,8 +0,0 @@ - -/** - * - */ -export default hooks => (simplifiedGeometry, lineSmoothing) => - lineSmoothing - ? hooks.smoothenGeometry(simplifiedGeometry) - : simplifiedGeometry diff --git a/src/renderer/ol/style/_symbolModifers.js b/src/renderer/ol/style/_symbolModifers.js deleted file mode 100644 index b2f97ff9..00000000 --- a/src/renderer/ol/style/_symbolModifers.js +++ /dev/null @@ -1,11 +0,0 @@ -import * as R from 'ramda' -import { MODIFIERS } from '../../symbology/2525c' - -/** - * - */ -export default (properties) => { - return Object.entries(properties) - .filter(([key, value]) => MODIFIERS[key] && value) - .reduce((acc, [key, value]) => R.tap(acc => (acc[MODIFIERS[key]] = value), acc), {}) -} diff --git a/src/renderer/ol/style/_transform.js b/src/renderer/ol/style/_transform.js deleted file mode 100644 index 874297fc..00000000 --- a/src/renderer/ol/style/_transform.js +++ /dev/null @@ -1,8 +0,0 @@ -import * as R from 'ramda' -import { transform } from '../../model/geometry' -import { destructure } from '../../../shared/signal' - -export default R.compose( - destructure(['read', 'write', 'pointResolution']), - R.map(transform) -) diff --git a/src/renderer/ol/style/chaikin.js b/src/renderer/ol/style/chaikin.js deleted file mode 100644 index f807ebbd..00000000 --- a/src/renderer/ol/style/chaikin.js +++ /dev/null @@ -1,48 +0,0 @@ -import * as R from 'ramda' -import { Polygon, LineString, MultiPolygon, MultiLineString, GeometryCollection } from 'ol/geom' - -const lerp = t => (v0, v1) => v0 * (1 - t) + v1 * t -const lerpB = lerp(0.25) -const lerpXY = ([[x1, y1], [x2, y2]]) => [ - [lerpB(x1, x2), lerpB(y1, y2)], - [lerpB(x2, x1), lerpB(y2, y1)] -] - -const chaikinLine = (coords, n) => { - if (n === 0) return coords - - const xs = R.dropLast(1, coords) - .map(([x1, y1], index) => [[x1, y1], coords[index + 1]]) - .flatMap(lerpXY) - - return chaikinLine([R.head(coords), ...xs, R.last(coords)], n - 1) -} - -const chaikinRing = (coords, n) => { - if (n === 0) return coords - - const xs = coords - .map(([x1, y1], index) => [[x1, y1], coords[(index + 1) % coords.length]]) - .flatMap(lerpXY) - - return chaikinRing(xs, n - 1) -} - -const K = v => fn => { fn(v); return v } -const I = v => v - -const closeRing = coords => K(coords)(coords => coords.push(coords[0])) -const smoothRing = n => ring => closeRing(chaikinRing(R.dropLast(1, ring), n)) -const smoothPolygon = n => polygon => polygon.map(smoothRing(n)) -const smoothLine = n => line => chaikinLine(line, n) -const smoothCollection = n => geometry => geometry.getGeometries().map(geometry => smooth(geometry, n)) - -const mappers = n => ({ - Polygon: geometry => new Polygon(geometry.getCoordinates().map(smoothRing(n))), - MultiPolygon: geometry => new MultiPolygon(geometry.getCoordinates().map(smoothPolygon(n))), - LineString: geometry => new LineString(smoothLine(n)(geometry.getCoordinates())), - MultiLineString: geometry => new MultiLineString(geometry.getCoordinates().map(smoothLine(n))), - GeometryCollection: geometry => new GeometryCollection(smoothCollection(n)(geometry)) -}) - -export const smooth = (geometry, n = 3) => (mappers(n)[geometry.getType()] || I)(geometry) diff --git a/src/renderer/ol/style/color-schemes.js b/src/renderer/ol/style/color-schemes.js deleted file mode 100644 index 44a18363..00000000 --- a/src/renderer/ol/style/color-schemes.js +++ /dev/null @@ -1,42 +0,0 @@ -import * as R from 'ramda' - -/** 2525C, table TABLE XIII */ -const schemes = { - dark: { - red: '#C80000', // RGB(200, 0, 0) - blue: '#006B8C', // RGB(0, 107, 140) - green: '#00A000', // RGB(0, 160, 0) - yellow: '#E1DC00', // RGB(225, 220, 0) - purple: '#500050' // RGB(80, 0, 80) - }, - medium: { - red: '#FF3031', // RGB(255, 48, 49) - blue: '#00A8DC', // RGB(0, 168, 220) - green: '#00E200', // RGB(0, 226, 0) - yellow: '#FFFF00', // RGB(255, 255, 0) - purple: '#800080' // RGB(128, 0, 128) - }, - light: { - red: '#FF8080', // RGB(255, 128, 128) - blue: '#80E0FF', // RGB(128, 224, 255) - green: '#AAFFAA', // RGB(170, 255, 170) - yellow: '#FFFF80', // RGB(255, 255, 128) - purple: '#FFA1FF' // RGB(255, 161, 255) - } -} - -const includes = xs => x => xs.includes(x) - -export const lineColor = scheme => R.cond([ - [includes(['A', 'F', 'M', 'D']), R.always(schemes[scheme].blue)], - [includes(['H', 'J', 'K', 'S']), R.always(schemes[scheme].red)], - [includes(['N', 'L']), R.always(schemes[scheme].green)], - [includes(['U', 'P', 'G', 'W']), R.always(schemes[scheme].yellow)], - [R.T, R.always('black')] -]) - -export const lineHaloColor = R.cond([ - [R.equals('-'), R.always('white')], - [R.T, R.always('black')] -]) - diff --git a/src/renderer/ol/style/corridor.js b/src/renderer/ol/style/corridor.js deleted file mode 100644 index 910e99e8..00000000 --- a/src/renderer/ol/style/corridor.js +++ /dev/null @@ -1,55 +0,0 @@ -/* eslint-disable camelcase */ -import * as shared from './shared' -import styles from './corridor-styles' -import { transform } from '../../model/geometry' - -const collectStyles = [next => { - const { parameterizedSIDC: sidc } = next - const dynamicStyle = (styles[sidc] || styles.DEFAULT) - const staticStyles = [] - return { dynamicStyle, staticStyles } -}, ['parameterizedSIDC']] - -/** - * geometry :: jsts/geom/geometry - * write :: jsts/geom/geometry -> ol/geom/geometry - * resolution :: Number - */ -const geometry = [next => { - const { definingGeometry, centerResolution } = next - - // Transform (TS/UTM). - // - const { read, write, pointResolution } = transform(definingGeometry) - const geometry = read(definingGeometry) - const simplifiedGeometry = geometry - const resolution = pointResolution(centerResolution) - const rewrite = ({ geometry, ...props }) => ({ geometry: write(geometry), ...props }) - return { geometry, simplifiedGeometry, rewrite, resolution } -}, ['mode', 'smoothen', 'geometryKey', 'centerResolution']] - - -const labelPlacement = [() => { - return { placement: x => x } -}, ['geometry']] - - -/** - * style :: [ol/style/Style] - */ -const error = [next => { - return { styles: styles.ERROR(next) } -}, ['err']] - -export default [ - shared.sidc, - shared.evalSync, - collectStyles, - shared.effectiveStyle, - geometry, - labelPlacement, - shared.selectedStyles, - shared.styles, - error, - shared.style -] diff --git a/src/renderer/ol/style/crosshair.js b/src/renderer/ol/style/crosshair.js deleted file mode 100644 index 6f1c25cb..00000000 --- a/src/renderer/ol/style/crosshair.js +++ /dev/null @@ -1,20 +0,0 @@ -import { Stroke, Circle, RegularShape, Style } from 'ol/style' - -export default (color, radius = 30) => { - const stroke = new Stroke({ color, width: 2 }) - const bigCircle = new Circle({ stroke, radius: 30 }) - const smallCircle = new Circle({ stroke, radius: radius / 15 }) - - return [ - new Style({ image: bigCircle }), - new Style({ image: smallCircle }), - ...[0, 1, 2, 3].map(direction => new Style({ - image: new RegularShape({ - stroke, - rotation: direction * Math.PI / 2, - points: 2, - radius: radius / 2, - displacement: [0, 0.8 * radius] - }) - }))] -} diff --git a/src/renderer/ol/style/echelon.js b/src/renderer/ol/style/echelon.js deleted file mode 100644 index 1bf087b6..00000000 --- a/src/renderer/ol/style/echelon.js +++ /dev/null @@ -1,53 +0,0 @@ -import * as R from 'ramda' -import { echelonCode } from '../../symbology/2525c' -import A from './resources/A.png' -import B from './resources/B.png' -import C from './resources/C.png' -import D from './resources/D.png' -import E from './resources/E.png' -import F from './resources/F.png' -import G from './resources/G.png' -import H from './resources/H.png' -import I from './resources/I.png' -import J from './resources/J.png' -import K from './resources/K.png' -import L from './resources/L.png' -import M from './resources/M.png' -import N from './resources/N.png' - -export const echelons = { - A: { width: 24, height: 25, url: A, text: '∅' }, - B: { width: 12, height: 12, url: B, text: '●' }, - C: { width: 26, height: 12, url: C, text: '●●' }, - D: { width: 40, height: 12, url: D, text: '●●●' }, - E: { width: 6, height: 29, url: E, text: '❙' }, - F: { width: 28, height: 29, url: F, text: ' ❙ ❙ ' }, - G: { width: 50, height: 29, url: G, text: ' ❙ ❙ ❙ ' }, - H: { width: 26, height: 29, url: H, text: 'X' }, - I: { width: 64, height: 29, url: I, text: 'X X' }, - J: { width: 101, height: 29, url: J, text: 'X X X' }, - K: { width: 138, height: 29, url: K, text: 'X X X X' }, - L: { width: 176, height: 29, url: L, text: 'X X X X X' }, - M: { width: 213, height: 29, url: M, text: 'X X X X X X' }, - N: { width: 65, height: 24, url: N, text: ' ++ ' } -} - -export default context => { - const { sidc } = context - const code = echelonCode(sidc) - const echelon = echelons[code] - - if (!echelon) return R.identity - - return props => { - const iconImage = props['icon-image'] - if (iconImage !== 'echelon') return props - return { - ...props, - 'icon-height': echelon.height, - 'icon-width': echelon.width, - 'icon-url': echelon.url, - 'icon-scale': 0.4 - } - } -} diff --git a/src/renderer/ol/style/labels.js b/src/renderer/ol/style/labels.js deleted file mode 100644 index f14db774..00000000 --- a/src/renderer/ol/style/labels.js +++ /dev/null @@ -1,116 +0,0 @@ -import * as R from 'ramda' -import { Jexl } from 'jexl' -const canvas = document.createElement('canvas') -const context = canvas.getContext('2d') - -/** - * - */ -const textBoundingBox = ({ TS, PI_OVER_2, resolution }, props) => { - const textField = props['text-field'] - if (!textField) return null - if (props['text-clipping'] === 'none') return null - - // Prepare bounding box geometry (dimensions only, including padding). - const lines = textField.split('\n') - const [maxWidthPx, maxHeightPx] = lines.reduce((acc, line) => { - context.font = props['text-font'] - const metrics = context.measureText(line) - const width = metrics.width - const height = 1.2 * lines.length * ((metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent)) - if (width > acc[0]) acc[0] = width - if (height > acc[1]) acc[1] = height - return acc - }, [0, 0]) - - const { x, y } = props.geometry.getCoordinates()[0] - const padding = props['text-padding'] || 0 - const dx = (maxWidthPx / 2 + padding) * resolution - const dy = (maxHeightPx / 2 + padding) * resolution - - const x1 = x - dx - const x2 = x + dx - const y1 = y - dy - const y2 = y + dy - const points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]] - const geometry = TS.polygon(points.map(TS.coordinate)) - - // Transform geometry (rotate/translate) to match - // label options offset, justify and rotate. - - const rotate = props['text-rotate'] || 0 - const justify = props['text-justify'] || 'center' - const [offsetX, offsetY] = props['text-offset'] || [0, 0] - - const flipX = { start: -1, end: 1, center: 0 } - const flipY = rotate < -PI_OVER_2 || rotate > PI_OVER_2 ? -1 : 1 - const tx = (-offsetX + flipX[justify] * (maxWidthPx / 2)) * resolution - const ty = flipY * offsetY * resolution - - const theta = 2 * Math.PI - rotate - const at = TS.AffineTransformation.translationInstance(-(x + tx), -(y + ty)) - at.rotate(theta) - at.translate(x, y) - - return at.transform(geometry) -} - - -/** - * - */ -const iconBoundingBox = ({ TS, resolution }, props) => { - const scale = props['icon-scale'] - if (!scale) return null - - const width = props['icon-width'] * scale / 4 - const height = props['icon-height'] * scale / 4 - const rotate = props['icon-rotate'] || 0 - const padding = props['icon-padding'] || 0 - const { x, y } = props.geometry.getCoordinates()[0] - - const x1 = x - (width + padding) * resolution - const x2 = x + (width + padding) * resolution - const y1 = y - (height + padding) * resolution - const y2 = y + (height + padding) * resolution - const points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]] - const theta = 2 * Math.PI - rotate - const geometry = TS.polygon(points.map(TS.coordinate)) - const rotation = TS.AffineTransformation.rotationInstance(theta, x, y) - return rotation.transform(geometry) -} - - -/** - * - */ -export const boundingBox = R.curry((context, style) => { - if (style['text-field']) return textBoundingBox(context, style) - else if (style['icon-image']) return iconBoundingBox(context, style) - else return null -}) - -const jexl = new Jexl() - -/** - * - */ -export const evalSync = context => { - - const evalSync = textField => Array.isArray(textField) - ? textField.map(evalSync).filter(Boolean).join('\n') - : jexl.evalSync(textField, context) - - return props => { - props = Array.isArray(props) ? props : [props] - return props.reduce((acc, spec) => { - if (!spec['text-field']) acc.push(spec) - else { - const textField = evalSync(spec['text-field']) - if (textField) acc.push({ ...spec, 'text-field': textField }) - } - - return acc - }, []) - } -} diff --git a/src/renderer/ol/style/linestring-placement.js b/src/renderer/ol/style/linestring-placement.js deleted file mode 100644 index 71dedfe2..00000000 --- a/src/renderer/ol/style/linestring-placement.js +++ /dev/null @@ -1,50 +0,0 @@ -import * as R from 'ramda' -import * as TS from '../ts' - -export const placement = (geometry) => { - const segments = TS.segments(geometry) - const line = TS.lengthIndexedLine(geometry) - const endIndex = line.getEndIndex() - const coordAt = (fraction, offset = 0) => line.extractPoint(endIndex * fraction + offset) - const pointAt = (fraction, offset = 0) => TS.point(coordAt(fraction, offset)) - const numPoints = geometry.getNumPoints() - - const segment = fraction => TS.segment([ - coordAt(fraction, -0.05), - coordAt(fraction, +0.05) - ]) - - const angle = anchor => { - if (!anchor) return segment(0.5).angle() - if (isNaN(anchor)) { - if (anchor.includes('center')) return segment(0.5).angle() - else if (anchor.includes('left')) return R.head(segments).angle() - else if (anchor.includes('right')) return R.last(segments).angle() - } else return segment(anchor).angle() - } - - const anchors = anchor => { - if (isNaN(anchor)) { - if (anchor.includes('center')) return pointAt(0.5) - else if (anchor.includes('left')) return geometry.getPointN(0) - else if (anchor.includes('right')) return geometry.getPointN(numPoints - 1) - else return pointAt(0.5) - } else return pointAt(anchor) - } - - const normalize = angle => TS.Angle.normalize(TS.Angle.PI_TIMES_2 - angle) - - return props => { - const rotate = props['text-field'] ? 'text-rotate' : 'icon-rotate' - const anchor = props['text-anchor'] || - props['icon-anchor'] || - props['symbol-anchor'] || - (props['text-field'] ? 'center' : null) - - return { - geometry: anchors(anchor), - ...props, - [rotate]: normalize(angle(anchor)) - } - } -} \ No newline at end of file diff --git a/src/renderer/ol/style/linestring.js b/src/renderer/ol/style/linestring.js deleted file mode 100644 index b37c75d3..00000000 --- a/src/renderer/ol/style/linestring.js +++ /dev/null @@ -1,36 +0,0 @@ -import * as shared from './shared' -import styles from './linestring-styles' -import { placement } from './linestring-placement' -import { labels } from './linestring-styles/labels' - -/** - * dynamicStyle - * staticStyles - */ -const collectStyles = [next => { - const { parameterizedSIDC: sidc } = next - const dynamicStyle = (styles[sidc] || styles.DEFAULT) - const staticStyles = (labels[sidc] || []) - return { dynamicStyle, staticStyles } -}, ['parameterizedSIDC']] - - -/** - * placement - */ -const labelPlacement = [next => { - return { placement: placement(next) } -}, ['geometry']] - - -export default [ - shared.sidc, - shared.evalSync, - collectStyles, - shared.effectiveStyle, - shared.geometry, - labelPlacement, - shared.selectedStyles, - shared.styles, - shared.style -] diff --git a/src/renderer/ol/style/multipoint.js b/src/renderer/ol/style/multipoint.js deleted file mode 100644 index 05de8182..00000000 --- a/src/renderer/ol/style/multipoint.js +++ /dev/null @@ -1,67 +0,0 @@ -/* eslint-disable no-multi-spaces */ -/* eslint-disable camelcase */ - -import * as shared from './shared' -import styles from './multipoint-styles' -import { transform } from '../../model/geometry' -import { placement } from './polygon-placement' -import { labels } from './multipoint-styles/labels' - -/** - * dynamicStyle - * staticStyles - */ -const collectStyles = [next => { - const { parameterizedSIDC: sidc } = next - const dynamicStyle = (styles[sidc] || styles.DEFAULT) - const staticStyles = (labels[sidc] || []) - return { dynamicStyle, staticStyles } -}, ['parameterizedSIDC']] - - -/** - * geometry :: jsts/geom/geometry - * rewrite :: ... - * resolution :: Number - */ -const geometry = [next => { - const { definingGeometry, centerResolution } = next - - // Transform (TS/UTM). - // - const { read, write, pointResolution } = transform(definingGeometry) - const geometry = read(definingGeometry) - const simplifiedGeometry = geometry - const resolution = pointResolution(centerResolution) - const rewrite = ({ geometry, ...props }) => ({ geometry: write(geometry), ...props }) - return { geometry, simplifiedGeometry, rewrite, resolution } -}, ['mode', 'smoothen', 'geometryKey', 'centerResolution']] - - -/** - * placement - */ -const labelPlacement = [next => { - // Explicit labels are exclusively for circular features. - // We use polygon placement to place these labels based on - // the point buffer around the features center. - // - const { TS, geometry } = next - const [C, A] = TS.coordinates(geometry) - const segment = TS.segment([C, A]) - const buffer = TS.pointBuffer(TS.point(C))(segment.getLength()) - return { placement: placement({ TS, geometry: buffer }) } -}, ['geometry']] - - -export default [ - shared.sidc, - shared.evalSync, - collectStyles, - shared.effectiveStyle, - geometry, - labelPlacement, - shared.selectedStyles, - shared.styles, - shared.style -] diff --git a/src/renderer/ol/style/patterns.js b/src/renderer/ol/style/patterns.js deleted file mode 100644 index e04616ac..00000000 --- a/src/renderer/ol/style/patterns.js +++ /dev/null @@ -1,90 +0,0 @@ -import { DEVICE_PIXEL_RATIO } from 'ol/has' -import { DEG2RAD } from '../../../shared/Math' - -const patterns = { - hatch: { - width: 5, - height: 5, - lines: [[0, 2.5, 5, 2.5]] - }, - cross: { - width: 7, - height: 7, - lines: [[0, 3, 10, 3], [3, 0, 3, 10]] - } -} - -const patternDescriptor = options => { - const d = Math.round(options.spacing) || 10 - const pattern = patterns[options.pattern] - - let a = Math.round(((options.angle || 0) - 90) % 360) - if (a > 180) a -= 360 - a *= DEG2RAD - const cos = Math.cos(a) - const sin = Math.sin(a) - if (Math.abs(sin) < 0.0001) { - pattern.width = pattern.height = d - pattern.lines = [[0, 0.5, d, 0.5]] - pattern.repeat = [[0, 0], [0, d]] - } else if (Math.abs(cos) < 0.0001) { - pattern.width = pattern.height = d - pattern.lines = [[0.5, 0, 0.5, d]] - pattern.repeat = [[0, 0], [d, 0]] - if (options.pattern === 'cross') { - pattern.lines.push([0, 0.5, d, 0.5]) - pattern.repeat.push([0, d]) - } - } else { - const w = pattern.width = Math.round(Math.abs(d / sin)) || 1 - const h = pattern.height = Math.round(Math.abs(d / cos)) || 1 - if (options.pattern === 'cross') { - pattern.lines = [[-w, -h, 2 * w, 2 * h], [2 * w, -h, -w, 2 * h]] - pattern.repeat = [[0, 0]] - } else if (cos * sin > 0) { - pattern.lines = [[-w, -h, 2 * w, 2 * h]] - pattern.repeat = [[0, 0], [w, 0], [0, h]] - } else { - pattern.lines = [[2 * w, -h, -w, 2 * h]] - pattern.repeat = [[0, 0], [-w, 0], [0, h]] - } - } - pattern.stroke = options.size === 0 ? 0 : options.size || 4 - return pattern -} - -export const fill = options => { - const canvas = document.createElement('canvas') - const context = canvas.getContext('2d') - const descriptor = patternDescriptor(options) - - canvas.width = Math.round(descriptor.width * DEVICE_PIXEL_RATIO) - canvas.height = Math.round(descriptor.height * DEVICE_PIXEL_RATIO) - context.scale(DEVICE_PIXEL_RATIO, DEVICE_PIXEL_RATIO) - context.lineCap = 'round' - - ;[ - [options.strokeColor, options.strokeWidth], - [options.strokeFillColor, options.strokeFillWidth] - ].forEach(([strokeStyle, lineWidth]) => { - context.lineWidth = lineWidth - context.strokeStyle = strokeStyle - const repeat = descriptor.repeat || [[0, 0]] - - if (descriptor.lines) { - for (let i = 0; i < descriptor.lines.length; i++) { - for (let r = 0; r < repeat.length; r++) { - const line = descriptor.lines[i] - context.beginPath() - context.moveTo(line[0] + repeat[r][0], line[1] + repeat[r][1]) - for (let k = 2; k < line.length; k += 2) { - context.lineTo(line[k] + repeat[r][0], line[k + 1] + repeat[r][1]) - } - context.stroke() - } - } - } - }) - - return context.createPattern(canvas, 'repeat') -} diff --git a/src/renderer/ol/style/point.js b/src/renderer/ol/style/point.js deleted file mode 100644 index 0c4372e2..00000000 --- a/src/renderer/ol/style/point.js +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable camelcase */ -import * as R from 'ramda' -import * as shared from './shared' -import { styleFactory } from './styleFactory' -import { MODIFIERS } from '../../symbology/2525c' - -const symbolModifiers = properties => Object.entries(properties) - .filter(([key, value]) => MODIFIERS[key] && value) - .reduce((acc, [key, value]) => R.tap(acc => (acc[MODIFIERS[key]] = value), acc), {}) - -/** - * style - */ -const style = [next => { - const { mode, definingGeometry, sidc, modifiers, effectiveStyle } = next - - // symbol-text-color - const selected = mode === 'singleselect' - ? { 'symbol-halo-color': 'white', 'symbol-halo-width': 6, 'symbol-fill-opacity': 1 } - : {} - - const style = [{ - id: 'style:2525c/symbol', - geometry: definingGeometry, - 'symbol-code': sidc, - 'symbol-modifiers': symbolModifiers(modifiers), - ...selected - }] - .map(effectiveStyle) - .flatMap(styleFactory) - - return { style } -}, ['mode', 'sidc', 'modifiers', 'geometryKey', 'effectiveStyle']] - - -export default [ - shared.sidc, - shared.effectiveStyle, - style -] diff --git a/src/renderer/ol/style/polygon-placement.js b/src/renderer/ol/style/polygon-placement.js deleted file mode 100644 index 93ba8d65..00000000 --- a/src/renderer/ol/style/polygon-placement.js +++ /dev/null @@ -1,57 +0,0 @@ -import * as TS from '../ts' - -const lazy = function (fn) { - let evaluated = false - let value - - return function () { - if (evaluated) return value - value = fn.apply(this, arguments) - evaluated = true - return value - } -} - -export const placement = (geometry) => { - const ring = geometry.getExteriorRing() - const envelope = ring.getEnvelopeInternal() - const centroid = TS.centroid(ring) - const [minX, maxX] = [envelope.getMinX(), envelope.getMaxX()] - const [minY, maxY] = [envelope.getMinY(), envelope.getMaxY()] - - const xIntersection = lazy(() => { - const coord = x => TS.coordinate(x, centroid.y) - const axis = TS.lineString([minX, maxX].map(coord)) - return TS.intersection([geometry, axis]).getCoordinates() - }) - - const yIntersection = lazy(() => { - const coord = y => TS.coordinate(centroid.x, y) - const axis = TS.lineString([minY, maxY].map(coord)) - return TS.intersection([geometry, axis]).getCoordinates() - }) - - const fraction = factor => { - const lengthIndexedLine = TS.lengthIndexedLine(ring) - const length = lengthIndexedLine.getEndIndex() - const coord = lengthIndexedLine.extractPoint(factor * length) - return TS.point(coord) - } - - const anchors = { - center: lazy(() => TS.point(centroid)), - bottom: lazy(() => TS.point(yIntersection()[0])), - top: lazy(() => TS.point(yIntersection()[1])), - left: lazy(() => TS.point(xIntersection()[0])), - right: lazy(() => TS.point(xIntersection()[1])) - } - - return props => { - const anchor = props['text-anchor'] - const geometry = Number.isFinite(anchor) - ? fraction(anchor) - : anchors[anchor || 'center']() - - return { geometry, ...props } - } -} diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js deleted file mode 100644 index 632ca65d..00000000 --- a/src/renderer/ol/style/polygon.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable camelcase */ -import * as shared from './shared' -import styles from './polygon-styles' -import { placement } from './polygon-placement' -import { labels } from './polygon-styles/labels' - -/** - * dynamicStyle - * staticStyles - */ -const collectStyles = [next => { - const { parameterizedSIDC: sidc } = next - const dynamicStyle = (styles[sidc] || styles.DEFAULT) - const staticStyles = (labels[sidc] || []) - return { dynamicStyle, staticStyles } -}, ['parameterizedSIDC']] - - -/** - * placement - */ -const labelPlacement = [next => { - return { placement: placement(next) } -}, ['geometry']] - - -/** - * style :: [ol/style/Style] - */ -const error = [next => { - return { styles: styles.ERROR(next) } -}, ['err']] - -export default [ - shared.sidc, - shared.evalSync, - collectStyles, - shared.effectiveStyle, - shared.geometry, - labelPlacement, - shared.selectedStyles, - shared.styles, - error, - shared.style -] diff --git a/src/renderer/ol/style/rules.js b/src/renderer/ol/style/rules.js deleted file mode 100644 index 3bfc372f..00000000 --- a/src/renderer/ol/style/rules.js +++ /dev/null @@ -1,56 +0,0 @@ -import * as R from 'ramda' -import isEqual from 'react-fast-compare' -import Point from './point' -import LineString from './linestring' -import Polygon from './polygon' -import Corridor from './corridor' -import MultiPoint from './multipoint' - -export const rules = { - Point, - LineString, - Polygon, - 'LineString:Point': Corridor, - MultiPoint -} - -const notEqual = (state, obj, key) => !isEqual(state[key], obj[key]) - -const comparators = { - globalStyle: notEqual, - layerStyle: notEqual, - featureStyle: notEqual, - properties: notEqual, - modifiers: notEqual -} - -const fn = rule => rule[0] -const deps = rule => rule[1] - - -/** - * - */ -export const reduce = (state, facts, rank = 0) => { - const next = { ...state, ...facts } - const different = key => comparators[key] - ? comparators[key](state, facts, key) - : state[key] !== facts[key] - - if (rank >= state.rules.length) return next - const changed = Object.keys(facts).filter(different) - if (changed.length === 0) return state - - const isStale = rule => deps(rule).some(key => changed.includes(key)) - const isFulfilled = rule => deps(rule).every(key => !R.isNil(next[key])) - const head = state.rules[rank] - const catcher = (err, next) => { - console.error(err) - return reduce(state, { ...next, err }, ++rank) - } - const tryer = next => isStale(head) && isFulfilled(head) - ? reduce(state, { ...next, ...fn(head)(next) }, ++rank) - : reduce(state, next, ++rank) - - return R.tryCatch(tryer, catcher)(next) -} diff --git a/src/renderer/ol/style/shared.js b/src/renderer/ol/style/shared.js deleted file mode 100644 index 556706a4..00000000 --- a/src/renderer/ol/style/shared.js +++ /dev/null @@ -1,188 +0,0 @@ -import * as Colors from './color-schemes' -import { echelonCode, identityCode, statusCode, parameterized } from '../../symbology/2525c' -import { smooth } from './chaikin' -import { transform } from '../../model/geometry' -import { styleFactory } from './styleFactory' -import echelon, { echelons } from './echelon' -import * as Labels from './labels' -import styleRegistry from './styleRegistry' - -const rules = [] -export default rules - - -/** - * sidc :: String - * parameterizedSIDC :: String - * modifiers :: [String] - */ -export const sidc = [next => { - const { properties } = next - const { sidc, ...modifiers } = properties - const parameterizedSIDC = parameterized(sidc) - return { sidc, parameterizedSIDC, modifiers } -}, ['properties']] - - -export const evalSync = [next => { - const { modifiers, sidc } = next - const sizeCode = echelonCode(sidc) - const echelonText = (sizeCode === '*' || sizeCode === '-') ? '' : echelons[sizeCode]?.text - return { evalSync: Labels.evalSync({ modifiers, echelon: echelonText }) } -}, ['modifiers', 'sidc']] - - -/** - * effectiveStyle :: ... - * smoothen :: boolean - */ -export const effectiveStyle = [next => { - const global = next.globalStyle || {} - const layer = next.layerStyle - const feature = next.featureStyle || {} - const { sidc } = next - const status = statusCode(sidc) - const identity = identityCode(sidc) - const simpleIdentity = identity === 'H' || identity === 'S' - ? 'H' - : '-' - - const colorScheme = feature?.['color-scheme'] || - layer?.['color-scheme'] || - global?.['color-scheme'] || - 'medium' - - const scheme = { - 'binary-color': Colors.lineColor(colorScheme)(simpleIdentity), // black or red - 'line-color': Colors.lineColor(colorScheme)(identity), - 'fill-color': Colors.lineColor(colorScheme)(identity), - 'line-dash-array': status === 'A' ? [20, 10] : null, - 'line-halo-color': Colors.lineHaloColor(identity), - 'line-halo-dash-array': status === 'A' ? [20, 10] : null - } - - if (!layer['line-color']) delete layer['line-color'] - if (!layer['line-halo-color']) delete layer['line-halo-color'] - - // Split `smoothen` from rest. - // We don't want to calculate new geometries on color change. - const merged = { ...global, ...scheme, ...layer, ...feature } - const { 'line-smooth': smoothen, ...props } = merged - - return { - smoothen: !!smoothen, - effectiveStyle: styleRegistry(props) - } -}, ['sidc', 'globalStyle', 'layerStyle', 'featureStyle']] - - -/** - * geometry :: jsts/geom/geometry - * rewrite :: ... - * resolution :: Number - */ -export const geometry = [next => { - const { smoothen, definingGeometry, centerResolution, geometryType } = next - - // Simplify. - // Never simplify current selection. - const coordinates = definingGeometry.getCoordinates() - const simplified = - (geometryType === 'Polygon' && coordinates[0].length > 50) || - (geometryType === 'LineString' && coordinates.length > 50) - - const simplifiedGeometry = simplified - ? definingGeometry.simplify(centerResolution) - : definingGeometry - - // Smoothen. - // - const smoothenedGeometry = smoothen - ? smooth(simplifiedGeometry) - : simplifiedGeometry - - // Transform (TS/UTM). - // - const { read, write, pointResolution } = transform(smoothenedGeometry) - const geometry = read(smoothenedGeometry) - const resolution = pointResolution(centerResolution) - const rewrite = ({ geometry, ...props }) => ({ geometry: write(geometry), ...props }) - - return { - simplified, - geometry, - simplifiedGeometry: read(simplifiedGeometry), - rewrite, - resolution - } -}, ['mode', 'smoothen', 'geometryKey', 'centerResolution']] - - -/** - * styles :: ... - */ -export const styles = [next => { - const { dynamicStyle, staticStyles, evalSync, placement } = next - const styles = [ - ...dynamicStyle(next), - ...staticStyles - ] - .flatMap(evalSync) - .flatMap(placement) - - return { styles } -}, ['dynamicStyle', 'staticStyles', 'evalSync', 'placement']] - - -/** - * - */ -export const selectedStyles = [next => { - const { TS, mode, simplifiedGeometry, geometryType } = next - const selectedStyles = [] - - const guideline = mode === 'singleselect' - ? { id: 'style:guide-stroke', geometry: simplifiedGeometry } - : null - - const points = () => TS.points(simplifiedGeometry) - const handles = geometryType !== 'point' && mode !== 'default' - ? mode === 'singleselect' - ? { id: 'style:circle-handle', geometry: TS.multiPoint(points()) } - : { id: 'style:rectangle-handle', geometry: points()[0] } - : null - - guideline && selectedStyles.push(guideline) - handles && selectedStyles.push(handles) - - return { selectedStyles } -}, ['mode', 'simplifiedGeometry']] - - -/** - * style :: [ol/style/Style] - */ -export const style = [next => { - const { TS, styles, selectedStyles, effectiveStyle, rewrite } = next - if (styles.length === 0) return { style: [] } - const effectiveStyles = [...styles, ...selectedStyles] - .map(echelon(next)) - .map(effectiveStyle) - - const bboxes = effectiveStyles.map(Labels.boundingBox(next)).filter(Boolean) - const clipLine = effectiveStyles.some(props => props['text-clipping'] === 'line') - const lineString = geometry => TS.lineString(geometry.getCoordinates()) - const clip = geometry => TS.difference([geometry, ...bboxes]) - const geometry = clipLine - ? lineString(effectiveStyles[0].geometry) - : effectiveStyles[0].geometry - - // Replace primary geometry with clipped geometry: - effectiveStyles[0].geometry = clip(geometry) - - const style = effectiveStyles - .map(rewrite) - .flatMap(styleFactory) - - return { style } -}, ['styles', 'effectiveStyle', 'selectedStyles']] diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js new file mode 100644 index 00000000..3c927e68 --- /dev/null +++ b/src/renderer/ol/style/styles.js @@ -0,0 +1,10 @@ +import Signal from '@syncpoint/signal' + +export default feature => { + const { $ } = feature + + return Signal.link(() => { + console.log('[styles] preparing style...') + return null + }, [$.feature, $.centerResolution]) +} diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index 3136bd28..90c79c58 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -5,9 +5,10 @@ import GeoJSON from 'ol/format/GeoJSON' import * as Extent from 'ol/extent' import Signal from '@syncpoint/signal' import Emitter from '../../shared/emitter' +import { setCoordinates } from '../model/geometry' import { flatten, select, split, once } from '../../shared/signal' import * as ID from '../ids' -import { style } from '../ol/style/__styles' +import style from '../ol/style/styles' const format = new GeoJSON({ @@ -243,11 +244,6 @@ FeatureStore.prototype.wrapFeature = function (feature) { const featureId = feature.getId() const layerId = ID.layerId(featureId) - // console.log('higherFormation', feature.getProperties()) - - const t = feature.getProperties().t - if (t === '1 EST') console.log(featureId) - feature.$ = { feature: Signal.of(feature), globalStyle: Signal.of(this.globalStyle), @@ -256,17 +252,10 @@ FeatureStore.prototype.wrapFeature = function (feature) { centerResolution: Signal.of(this.resolution) } - // feature.setStyle([]) // no initial style const $style = style(feature) + $style.on(feature.setStyle.bind(feature)) once(() => this.emit('addfeatures', { features: [feature] }), $style) - $style.on(style => { - if (featureId === 'feature:2ea57605-8e1e-4c46-9ec1-8e7b162af538/7feeea69-074a-4fb3-8515-4cfd74b717d6') { - console.log(style) - } - feature.setStyle(style) - // feature.setStyle.bind(feature) - }) // Use dedicated function to update feature coordinates from within // modify interaction. Such internal changes must not trigger ModifyEvent. From 6a27161eec1d72c52b9eefec6204fb0b7d4d646f Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 1 Jul 2024 07:11:42 +0200 Subject: [PATCH 19/73] experimenting with vector source. --- src/renderer/components/map/vectorLayers.js | 5 +- src/renderer/components/map/vectorSources.js | 4 ++ src/renderer/model/Sources.js | 65 ++++++++++++++++++++ src/renderer/ol/style/styles.js | 1 - src/renderer/store/FeatureStore.js | 2 +- src/renderer/store/SpatialIndex.js | 9 +-- 6 files changed, 76 insertions(+), 10 deletions(-) diff --git a/src/renderer/components/map/vectorLayers.js b/src/renderer/components/map/vectorLayers.js index 33f30705..594cf9b7 100644 --- a/src/renderer/components/map/vectorLayers.js +++ b/src/renderer/components/map/vectorLayers.js @@ -25,7 +25,7 @@ const highlightLayer = (sources, styles) => { export default (sources, styles) => { - const { deselectedSource, selectedSource } = sources + const { deselectedSource, selectedSource, experimentalSource } = sources const declutter = false const vectorLayer = source => new VectorLayer({ source, @@ -36,6 +36,7 @@ export default (sources, styles) => { return { featureLayer: vectorLayer(deselectedSource), highlightLayer: highlightLayer(sources, styles), - selectedLayer: vectorLayer(selectedSource) + selectedLayer: vectorLayer(selectedSource), + experimentalLayer: vectorLayer(experimentalSource) } } diff --git a/src/renderer/components/map/vectorSources.js b/src/renderer/components/map/vectorSources.js index 93a43eac..95f63fa9 100644 --- a/src/renderer/components/map/vectorSources.js +++ b/src/renderer/components/map/vectorSources.js @@ -3,6 +3,9 @@ import * as ID from '../../ids' export default async services => { const { store, featureStore, emitter, sessionStore, selection } = services + + const experimentalSource = Sources.experimentalSource(store) + const featureSource = Sources.union( Sources.featureSource(featureStore, ID.FEATURE_SCOPE), Sources.featureSource(featureStore, ID.MARKER_SCOPE), @@ -17,6 +20,7 @@ export default async services => { const modifiableSource = Sources.intersect(unlockedSource, selectedSource) return { + experimentalSource, featureSource, highlightSource, selectedSource, diff --git a/src/renderer/model/Sources.js b/src/renderer/model/Sources.js index 6b078f4c..dcfcddfb 100644 --- a/src/renderer/model/Sources.js +++ b/src/renderer/model/Sources.js @@ -3,8 +3,73 @@ import Collection from 'ol/Collection' import VectorSource from 'ol/source/Vector' import Feature from 'ol/Feature' import Event from 'ol/events/Event' +import GeoJSON from 'ol/format/GeoJSON' import * as ID from '../ids' +const reduce = async (store, prefix, fn, acc) => { + const db = store.db + const it = db.iterator({ gte: `${prefix}`, lte: `${prefix}\xff` }) + for await (const entry of it) acc = fn(acc, entry) + return acc +} + +const push = (acc, [key, value]) => { + acc.push(readFeature(({ id: key, ...value }))) + return acc +} + +const format = new GeoJSON({ + dataProjection: 'EPSG:3857', + featureProjection: 'EPSG:3857' +}) + +export const readFeature = source => format.readFeature(source) +export const readFeatures = source => format.readFeatures(source) +export const readGeometry = source => format.readGeometry(source) +export const writeGeometry = geometry => format.writeGeometry(geometry) +export const writeGeometryObject = geometry => format.writeGeometryObject(geometry) + +// writeFeatureCollection :: [ol/Feature] -> GeoJSON/FeatureCollection +export const writeFeatureCollection = features => format.writeFeaturesObject(features) +export const writeFeatureObject = feature => format.writeFeatureObject(feature) + +export const experimentalSource = store => { + const state = {} + + const index = new VectorSource({ useSpatialIndex: true, features: [] }) + + ;(async () => { + const now = Date.now() + const features = await reduce(store, ID.FEATURE_SCOPE, push, []) + index.addFeatures(features) + console.log((Date.now() - now), 'ms', features.length) + })() + + const loader = (extent, resolution, projection) => { + const now = Date.now() + state.projection = projection + const features = index.getFeaturesInExtent(extent) + source.clear() + source.addFeatures(features) + console.log((Date.now() - now), 'ms', features.length) + } + + const strategy = (extent, resolution) => { + const bbox = extent.join(',') + if (!state.projection) return [extent] + + if (bbox !== state.bbox) { + state.bbox = bbox + loader(extent, resolution, state.projection) + source.clear() + } + + return [extent] + } + + const source = new VectorSource({ useSpatialIndex: false, loader, strategy }) + return source +} /** * diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index 3c927e68..fb410e8c 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -4,7 +4,6 @@ export default feature => { const { $ } = feature return Signal.link(() => { - console.log('[styles] preparing style...') return null }, [$.feature, $.centerResolution]) } diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index 90c79c58..463836c7 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -6,7 +6,7 @@ import * as Extent from 'ol/extent' import Signal from '@syncpoint/signal' import Emitter from '../../shared/emitter' import { setCoordinates } from '../model/geometry' -import { flatten, select, split, once } from '../../shared/signal' +import { flatten, select, once } from '../../shared/signal' import * as ID from '../ids' import style from '../ol/style/styles' diff --git a/src/renderer/store/SpatialIndex.js b/src/renderer/store/SpatialIndex.js index 75d937bf..37c90fbe 100644 --- a/src/renderer/store/SpatialIndex.js +++ b/src/renderer/store/SpatialIndex.js @@ -4,18 +4,15 @@ import * as L from '../../shared/level' import { bbox } from './geometry' import * as TS from '../ol/ts' +/** + * Spatial index for point geometries only. + */ export function SpatialIndex (wkbDB) { this.wkbDB = wkbDB this.tree = new RBush() this.geoJSONReader = new TS.GeoJSONReader() wkbDB.on('batch', this.update.bind(this)) - // wkbDB.on('put', (key, value) => console.log('[SpatialIndex/put]', key, value)) - // wkbDB.on('del', key => console.log('[SpatialIndex/del]', key)) - - // Import symbols once for each fresh project database. - window.requestIdleCallback(async () => { - }, { timeout: 2000 }) } From 49636c21a9eb733607ac3db08dd826979ed37cef Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 1 Jul 2024 08:57:44 +0200 Subject: [PATCH 20/73] improved feature loading. --- src/renderer/components/map/vectorSources.js | 2 +- src/renderer/model/Sources.js | 85 ++++++++++++-------- 2 files changed, 54 insertions(+), 33 deletions(-) diff --git a/src/renderer/components/map/vectorSources.js b/src/renderer/components/map/vectorSources.js index 95f63fa9..d3afbcb1 100644 --- a/src/renderer/components/map/vectorSources.js +++ b/src/renderer/components/map/vectorSources.js @@ -4,7 +4,7 @@ import * as ID from '../../ids' export default async services => { const { store, featureStore, emitter, sessionStore, selection } = services - const experimentalSource = Sources.experimentalSource(store) + const experimentalSource = Sources.experimentalSource(services) const featureSource = Sources.union( Sources.featureSource(featureStore, ID.FEATURE_SCOPE), diff --git a/src/renderer/model/Sources.js b/src/renderer/model/Sources.js index dcfcddfb..f94551b2 100644 --- a/src/renderer/model/Sources.js +++ b/src/renderer/model/Sources.js @@ -1,4 +1,5 @@ import * as R from 'ramda' +import Signal from '@syncpoint/signal' import Collection from 'ol/Collection' import VectorSource from 'ol/source/Vector' import Feature from 'ol/Feature' @@ -6,52 +7,72 @@ import Event from 'ol/events/Event' import GeoJSON from 'ol/format/GeoJSON' import * as ID from '../ids' -const reduce = async (store, prefix, fn, acc) => { - const db = store.db - const it = db.iterator({ gte: `${prefix}`, lte: `${prefix}\xff` }) - for await (const entry of it) acc = fn(acc, entry) - return acc -} - -const push = (acc, [key, value]) => { - acc.push(readFeature(({ id: key, ...value }))) - return acc -} - const format = new GeoJSON({ dataProjection: 'EPSG:3857', featureProjection: 'EPSG:3857' }) -export const readFeature = source => format.readFeature(source) -export const readFeatures = source => format.readFeatures(source) -export const readGeometry = source => format.readGeometry(source) -export const writeGeometry = geometry => format.writeGeometry(geometry) -export const writeGeometryObject = geometry => format.writeGeometryObject(geometry) -// writeFeatureCollection :: [ol/Feature] -> GeoJSON/FeatureCollection -export const writeFeatureCollection = features => format.writeFeaturesObject(features) -export const writeFeatureObject = feature => format.writeFeatureObject(feature) +export const experimentalSource = services => { + const { store, emitter } = services + const state = { loaded: false } -export const experimentalSource = store => { - const state = {} + const readFeature = source => { + console.log(state) + const feature = format.readFeature(source) - const index = new VectorSource({ useSpatialIndex: true, features: [] }) + feature.$ = { + feature: Signal.of(feature), + globalStyle: Signal.of(state.globalStyle), + layerStyle: Signal.of({}), + featureStyle: Signal.of({}), + centerResolution: Signal.of(state.resolution) + } - ;(async () => { - const now = Date.now() - const features = await reduce(store, ID.FEATURE_SCOPE, push, []) - index.addFeatures(features) - console.log((Date.now() - now), 'ms', features.length) - })() + return feature + } + + const reduce = async (prefix, fn, acc) => { + const db = store.db + const it = db.iterator({ gte: `${prefix}`, lte: `${prefix}\xff` }) + for await (const entry of it) acc = fn(acc, entry) + return acc + } - const loader = (extent, resolution, projection) => { + const push = (acc, [key, value]) => { + acc.push(readFeature(({ id: key, ...value }))) + return acc + } + + const index = new VectorSource({ + useSpatialIndex: true, + features: [], + loader: async (extent, resolution, projection, success, failure) => { + console.log('[index] loading...') + } + }) + + const loader = async (extent, resolution, projection, success) => { const now = Date.now() + + state.extent = extent + state.resolution = resolution state.projection = projection + + if (!state.loaded) { + // pre-load and store global style + state.globalStyle = await store.value(ID.defaultStyleId) + const features = await reduce(ID.FEATURE_SCOPE, push, []) + index.addFeatures(features) + state.loaded = true + console.log('loaded features', features.length) + } + const features = index.getFeaturesInExtent(extent) source.clear() - source.addFeatures(features) - console.log((Date.now() - now), 'ms', features.length) + if (success) success(features) + else source.addFeatures(features) + console.log('features in view', features.length, 'time', (Date.now() - now), 'ms') } const strategy = (extent, resolution) => { From 64e796c57316c0c64b58786681b5fa527e4d7eca Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 1 Jul 2024 20:57:46 +0200 Subject: [PATCH 21/73] symbol styles. --- src/renderer/components/map/vectorLayers.js | 6 +- src/renderer/components/map/vectorSources.js | 14 +-- src/renderer/model/Sources.js | 82 ++++++++++-------- src/renderer/ol/style/_colorScheme.js | 10 +++ src/renderer/ol/style/_effectiveStyle.js | 14 +++ src/renderer/ol/style/_schemeStyle.js | 22 +++++ src/renderer/ol/style/color-schemes.js | 42 +++++++++ src/renderer/ol/style/graphics.js | 8 ++ src/renderer/ol/style/patterns.js | 90 ++++++++++++++++++++ src/renderer/ol/style/point.js | 24 ++++++ src/renderer/ol/style/styles.js | 24 +++++- 11 files changed, 286 insertions(+), 50 deletions(-) create mode 100644 src/renderer/ol/style/_colorScheme.js create mode 100644 src/renderer/ol/style/_effectiveStyle.js create mode 100644 src/renderer/ol/style/_schemeStyle.js create mode 100644 src/renderer/ol/style/color-schemes.js create mode 100644 src/renderer/ol/style/graphics.js create mode 100644 src/renderer/ol/style/patterns.js create mode 100644 src/renderer/ol/style/point.js diff --git a/src/renderer/components/map/vectorLayers.js b/src/renderer/components/map/vectorLayers.js index 594cf9b7..448e2191 100644 --- a/src/renderer/components/map/vectorLayers.js +++ b/src/renderer/components/map/vectorLayers.js @@ -25,7 +25,7 @@ const highlightLayer = (sources, styles) => { export default (sources, styles) => { - const { deselectedSource, selectedSource, experimentalSource } = sources + const { deselectedSource, selectedSource, featureSource } = sources const declutter = false const vectorLayer = source => new VectorLayer({ source, @@ -34,9 +34,9 @@ export default (sources, styles) => { }) return { - featureLayer: vectorLayer(deselectedSource), + // featureLayer: vectorLayer(deselectedSource), + featureLayer: vectorLayer(featureSource), highlightLayer: highlightLayer(sources, styles), selectedLayer: vectorLayer(selectedSource), - experimentalLayer: vectorLayer(experimentalSource) } } diff --git a/src/renderer/components/map/vectorSources.js b/src/renderer/components/map/vectorSources.js index d3afbcb1..91959b36 100644 --- a/src/renderer/components/map/vectorSources.js +++ b/src/renderer/components/map/vectorSources.js @@ -4,13 +4,14 @@ import * as ID from '../../ids' export default async services => { const { store, featureStore, emitter, sessionStore, selection } = services - const experimentalSource = Sources.experimentalSource(services) + // const experimentalSource = Sources.experimentalSource(services) + const featureSource = Sources.experimentalSource(services) - const featureSource = Sources.union( - Sources.featureSource(featureStore, ID.FEATURE_SCOPE), - Sources.featureSource(featureStore, ID.MARKER_SCOPE), - Sources.featureSource(featureStore, ID.MEASURE_SCOPE) - ) + // const featureSource = Sources.union( + // Sources.featureSource(featureStore, ID.FEATURE_SCOPE), + // Sources.featureSource(featureStore, ID.MARKER_SCOPE), + // Sources.featureSource(featureStore, ID.MEASURE_SCOPE) + // ) const { visibleSource } = await Sources.visibilityTracker(featureSource, store, emitter) const { unlockedSource } = Sources.lockedTracker(featureSource, store) @@ -20,7 +21,6 @@ export default async services => { const modifiableSource = Sources.intersect(unlockedSource, selectedSource) return { - experimentalSource, featureSource, highlightSource, selectedSource, diff --git a/src/renderer/model/Sources.js b/src/renderer/model/Sources.js index f94551b2..f087b528 100644 --- a/src/renderer/model/Sources.js +++ b/src/renderer/model/Sources.js @@ -6,6 +6,7 @@ import Feature from 'ol/Feature' import Event from 'ol/events/Event' import GeoJSON from 'ol/format/GeoJSON' import * as ID from '../ids' +import styles from '../ol/style/styles' const format = new GeoJSON({ dataProjection: 'EPSG:3857', @@ -14,21 +15,25 @@ const format = new GeoJSON({ export const experimentalSource = services => { - const { store, emitter } = services + const { store } = services const state = { loaded: false } const readFeature = source => { - console.log(state) const feature = format.readFeature(source) + const featureId = feature.getId() + const layerId = ID.layerId(featureId) feature.$ = { feature: Signal.of(feature), - globalStyle: Signal.of(state.globalStyle), - layerStyle: Signal.of({}), - featureStyle: Signal.of({}), + globalStyle: Signal.of(state.styles[ID.defaultStyleId]), + layerStyle: Signal.of(state.styles[ID.styleId(layerId)] ?? {}), + featureStyle: Signal.of(state.styles[ID.styleId(featureId)] ?? {}), centerResolution: Signal.of(state.resolution) } + feature.$.styles = styles(feature) + feature.$.styles.on(feature.setStyle.bind(feature)) + return feature } @@ -46,49 +51,54 @@ export const experimentalSource = services => { const index = new VectorSource({ useSpatialIndex: true, - features: [], - loader: async (extent, resolution, projection, success, failure) => { - console.log('[index] loading...') - } + features: [] }) - const loader = async (extent, resolution, projection, success) => { - const now = Date.now() - - state.extent = extent - state.resolution = resolution - state.projection = projection - + const customLoader = async (extent) => { if (!state.loaded) { - // pre-load and store global style - state.globalStyle = await store.value(ID.defaultStyleId) + state.loaded = true + // Pre-load all styles for later use. + state.styles = await reduce('style+', (acc, [key, value]) => { + acc[key] = value + return acc + }, {}) + const features = await reduce(ID.FEATURE_SCOPE, push, []) index.addFeatures(features) - state.loaded = true - console.log('loaded features', features.length) } - const features = index.getFeaturesInExtent(extent) - source.clear() - if (success) success(features) - else source.addFeatures(features) - console.log('features in view', features.length, 'time', (Date.now() - now), 'ms') + const loaded = source.getFeatures() + const loading = index.getFeaturesInExtent(extent) + const difference = R.differenceWith((f1, f2) => f1.getId() === f2.getId()) + source.removeFeatures(difference(loaded, loading)) + source.addFeatures(difference(loading, loaded)) + + // Apply resolution to all features currently in view: + source.getFeatures() + .filter(feature => feature.$.resolution) + .forEach(feature => feature.$.resolution(state.resolution)) } + // NOTE: Standard bbox strategy only trigger loader if extent increases. + // This way features are never removed. + // See also: https://gis.stackexchange.com/questions/322681/openlayers-refresh-vector-source-with-bbox-strategy-after-map-moved + const strategy = (extent, resolution) => { const bbox = extent.join(',') - if (!state.projection) return [extent] - - if (bbox !== state.bbox) { - state.bbox = bbox - loader(extent, resolution, state.projection) - source.clear() - } - - return [extent] + if (bbox === state.bbox) return [] + state.bbox = bbox + state.resolution = resolution + customLoader(extent) + return [] } - const source = new VectorSource({ useSpatialIndex: false, loader, strategy }) + const source = new VectorSource({ + features: [], + useSpatialIndex: false, + loader: () => {}, + strategy + }) + return source } @@ -102,8 +112,6 @@ export const featureSource = (featureStore, scope) => { const source = new VectorSource({ features: [] }) - console.log('[featureSource] registering listeners...') - featureStore.on('addfeatures', ({ features }) => { source.addFeatures(features.filter(matchesScope)) }) diff --git a/src/renderer/ol/style/_colorScheme.js b/src/renderer/ol/style/_colorScheme.js new file mode 100644 index 00000000..3f84cdd2 --- /dev/null +++ b/src/renderer/ol/style/_colorScheme.js @@ -0,0 +1,10 @@ + +/** + * + */ +export default (globalStyle, layerStyle, featureStyle) => { + return featureStyle?.['color-scheme'] || + layerStyle?.['color-scheme'] || + globalStyle?.['color-scheme'] || + 'medium' +} diff --git a/src/renderer/ol/style/_effectiveStyle.js b/src/renderer/ol/style/_effectiveStyle.js new file mode 100644 index 00000000..6df90970 --- /dev/null +++ b/src/renderer/ol/style/_effectiveStyle.js @@ -0,0 +1,14 @@ + +/** + * + */ +export default (globalStyle, schemeStyle, layerStyle, featureStyle) => { + if (!layerStyle['line-color']) delete layerStyle['line-color'] + if (!layerStyle['line-halo-color']) delete layerStyle['line-halo-color'] + return { + ...globalStyle, + ...schemeStyle, + ...layerStyle, + ...featureStyle + } +} diff --git a/src/renderer/ol/style/_schemeStyle.js b/src/renderer/ol/style/_schemeStyle.js new file mode 100644 index 00000000..9a2afbd9 --- /dev/null +++ b/src/renderer/ol/style/_schemeStyle.js @@ -0,0 +1,22 @@ +import * as Colors from './color-schemes' +import { identityCode, statusCode } from '../../symbology/2525c' + +/** + * + */ +export default (sidc, colorScheme) => { + const status = statusCode(sidc) + const identity = identityCode(sidc) + const simpleIdentity = identity === 'H' || identity === 'S' + ? 'H' + : '-' + + return { + 'binary-color': Colors.lineColor(colorScheme)(simpleIdentity), // black or red + 'line-color': Colors.lineColor(colorScheme)(identity), + 'fill-color': Colors.lineColor(colorScheme)(identity), + 'line-dash-array': status === 'A' ? [20, 10] : null, + 'line-halo-color': Colors.lineHaloColor(identity), + 'line-halo-dash-array': status === 'A' ? [20, 10] : null + } +} diff --git a/src/renderer/ol/style/color-schemes.js b/src/renderer/ol/style/color-schemes.js new file mode 100644 index 00000000..44a18363 --- /dev/null +++ b/src/renderer/ol/style/color-schemes.js @@ -0,0 +1,42 @@ +import * as R from 'ramda' + +/** 2525C, table TABLE XIII */ +const schemes = { + dark: { + red: '#C80000', // RGB(200, 0, 0) + blue: '#006B8C', // RGB(0, 107, 140) + green: '#00A000', // RGB(0, 160, 0) + yellow: '#E1DC00', // RGB(225, 220, 0) + purple: '#500050' // RGB(80, 0, 80) + }, + medium: { + red: '#FF3031', // RGB(255, 48, 49) + blue: '#00A8DC', // RGB(0, 168, 220) + green: '#00E200', // RGB(0, 226, 0) + yellow: '#FFFF00', // RGB(255, 255, 0) + purple: '#800080' // RGB(128, 0, 128) + }, + light: { + red: '#FF8080', // RGB(255, 128, 128) + blue: '#80E0FF', // RGB(128, 224, 255) + green: '#AAFFAA', // RGB(170, 255, 170) + yellow: '#FFFF80', // RGB(255, 255, 128) + purple: '#FFA1FF' // RGB(255, 161, 255) + } +} + +const includes = xs => x => xs.includes(x) + +export const lineColor = scheme => R.cond([ + [includes(['A', 'F', 'M', 'D']), R.always(schemes[scheme].blue)], + [includes(['H', 'J', 'K', 'S']), R.always(schemes[scheme].red)], + [includes(['N', 'L']), R.always(schemes[scheme].green)], + [includes(['U', 'P', 'G', 'W']), R.always(schemes[scheme].yellow)], + [R.T, R.always('black')] +]) + +export const lineHaloColor = R.cond([ + [R.equals('-'), R.always('white')], + [R.T, R.always('black')] +]) + diff --git a/src/renderer/ol/style/graphics.js b/src/renderer/ol/style/graphics.js new file mode 100644 index 00000000..8e6398de --- /dev/null +++ b/src/renderer/ol/style/graphics.js @@ -0,0 +1,8 @@ +import Signal from '@syncpoint/signal' + +export default $ => { + + $.resolution = Signal.of() + + return Signal.of(null) +} \ No newline at end of file diff --git a/src/renderer/ol/style/patterns.js b/src/renderer/ol/style/patterns.js new file mode 100644 index 00000000..e04616ac --- /dev/null +++ b/src/renderer/ol/style/patterns.js @@ -0,0 +1,90 @@ +import { DEVICE_PIXEL_RATIO } from 'ol/has' +import { DEG2RAD } from '../../../shared/Math' + +const patterns = { + hatch: { + width: 5, + height: 5, + lines: [[0, 2.5, 5, 2.5]] + }, + cross: { + width: 7, + height: 7, + lines: [[0, 3, 10, 3], [3, 0, 3, 10]] + } +} + +const patternDescriptor = options => { + const d = Math.round(options.spacing) || 10 + const pattern = patterns[options.pattern] + + let a = Math.round(((options.angle || 0) - 90) % 360) + if (a > 180) a -= 360 + a *= DEG2RAD + const cos = Math.cos(a) + const sin = Math.sin(a) + if (Math.abs(sin) < 0.0001) { + pattern.width = pattern.height = d + pattern.lines = [[0, 0.5, d, 0.5]] + pattern.repeat = [[0, 0], [0, d]] + } else if (Math.abs(cos) < 0.0001) { + pattern.width = pattern.height = d + pattern.lines = [[0.5, 0, 0.5, d]] + pattern.repeat = [[0, 0], [d, 0]] + if (options.pattern === 'cross') { + pattern.lines.push([0, 0.5, d, 0.5]) + pattern.repeat.push([0, d]) + } + } else { + const w = pattern.width = Math.round(Math.abs(d / sin)) || 1 + const h = pattern.height = Math.round(Math.abs(d / cos)) || 1 + if (options.pattern === 'cross') { + pattern.lines = [[-w, -h, 2 * w, 2 * h], [2 * w, -h, -w, 2 * h]] + pattern.repeat = [[0, 0]] + } else if (cos * sin > 0) { + pattern.lines = [[-w, -h, 2 * w, 2 * h]] + pattern.repeat = [[0, 0], [w, 0], [0, h]] + } else { + pattern.lines = [[2 * w, -h, -w, 2 * h]] + pattern.repeat = [[0, 0], [-w, 0], [0, h]] + } + } + pattern.stroke = options.size === 0 ? 0 : options.size || 4 + return pattern +} + +export const fill = options => { + const canvas = document.createElement('canvas') + const context = canvas.getContext('2d') + const descriptor = patternDescriptor(options) + + canvas.width = Math.round(descriptor.width * DEVICE_PIXEL_RATIO) + canvas.height = Math.round(descriptor.height * DEVICE_PIXEL_RATIO) + context.scale(DEVICE_PIXEL_RATIO, DEVICE_PIXEL_RATIO) + context.lineCap = 'round' + + ;[ + [options.strokeColor, options.strokeWidth], + [options.strokeFillColor, options.strokeFillWidth] + ].forEach(([strokeStyle, lineWidth]) => { + context.lineWidth = lineWidth + context.strokeStyle = strokeStyle + const repeat = descriptor.repeat || [[0, 0]] + + if (descriptor.lines) { + for (let i = 0; i < descriptor.lines.length; i++) { + for (let r = 0; r < repeat.length; r++) { + const line = descriptor.lines[i] + context.beginPath() + context.moveTo(line[0] + repeat[r][0], line[1] + repeat[r][1]) + for (let k = 2; k < line.length; k += 2) { + context.lineTo(line[k] + repeat[r][0], line[k + 1] + repeat[r][1]) + } + context.stroke() + } + } + } + }) + + return context.createPattern(canvas, 'repeat') +} diff --git a/src/renderer/ol/style/point.js b/src/renderer/ol/style/point.js new file mode 100644 index 00000000..81e3fd13 --- /dev/null +++ b/src/renderer/ol/style/point.js @@ -0,0 +1,24 @@ +import * as R from 'ramda' +import Signal from '@syncpoint/signal' +import { MODIFIERS } from '../../symbology/2525c' +import { styleFactory } from './styleFactory' + +/** + * + */ +export default $ => Signal.link((feature, styleRegistry) => { + const { geometry, sidc, ...properties } = feature.getProperties() + + const modifiers = Object.entries(properties) + .filter(([key, value]) => MODIFIERS[key] && value) + .reduce((acc, [key, value]) => R.tap(acc => (acc[MODIFIERS[key]] = value), acc), {}) + + return [{ + id: 'style:2525c/symbol', + geometry, + 'symbol-code': sidc, + 'symbol-modifiers': modifiers + }] + .map(styleRegistry) + .flatMap(styleFactory) +}, [$.feature, $.styleRegistry]) diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index fb410e8c..86b3714e 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -1,9 +1,27 @@ +import * as R from 'ramda' import Signal from '@syncpoint/signal' +import * as Geometry from '../../model/geometry' +import styleRegistry from './styleRegistry' +import point from './point' +import graphic from './graphics' + +import colorScheme from './_colorScheme' +import schemeStyle from './_schemeStyle' +import effectiveStyle from './_effectiveStyle' export default feature => { const { $ } = feature - return Signal.link(() => { - return null - }, [$.feature, $.centerResolution]) + $.properties = $.feature.map(feature => feature.getProperties()) + $.geometry = $.properties.map(({ geometry }) => geometry) + $.sidc = $.properties.map(R.prop('sidc')) + $.colorScheme = Signal.link(colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) + $.schemeStyle = Signal.link(schemeStyle, [$.sidc, $.colorScheme]) + $.effectiveStyle = Signal.link(effectiveStyle, [$.globalStyle, $.schemeStyle, $.layerStyle, $.featureStyle]) + $.styleRegistry = $.effectiveStyle.map(styleRegistry) + + const geometryType = Geometry.geometryType(feature.getGeometry()) + return geometryType === 'Point' + ? point($) + : graphic($) } From 682a6a4dcd1acabc18100f8e5baf6b6348c735e1 Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 2 Jul 2024 10:17:58 +0200 Subject: [PATCH 22/73] forwarding style events. --- src/renderer/components/map/vectorLayers.js | 2 +- src/renderer/components/map/vectorSources.js | 25 +- src/renderer/model/Sources.js | 365 ------------------ .../model/sources/TouchFeaturesEvent.js | 11 + src/renderer/model/sources/__featureSource.js | 24 ++ src/renderer/model/sources/featureSource.js | 150 +++++++ src/renderer/model/sources/filter.js | 43 +++ .../model/sources/highlightTracker.js | 46 +++ src/renderer/model/sources/intersect.js | 31 ++ src/renderer/model/sources/lockedTracker.js | 36 ++ .../model/sources/selectionTracker.js | 24 ++ src/renderer/model/sources/union.js | 17 + .../model/sources/visibilityTracker.js | 48 +++ src/renderer/ol/style/_transform.js | 8 + src/renderer/ol/style/dynamic.js | 0 src/renderer/ol/style/graphics.js | 13 +- src/renderer/ol/style/styles.js | 2 +- 17 files changed, 466 insertions(+), 379 deletions(-) delete mode 100644 src/renderer/model/Sources.js create mode 100644 src/renderer/model/sources/TouchFeaturesEvent.js create mode 100644 src/renderer/model/sources/__featureSource.js create mode 100644 src/renderer/model/sources/featureSource.js create mode 100644 src/renderer/model/sources/filter.js create mode 100644 src/renderer/model/sources/highlightTracker.js create mode 100644 src/renderer/model/sources/intersect.js create mode 100644 src/renderer/model/sources/lockedTracker.js create mode 100644 src/renderer/model/sources/selectionTracker.js create mode 100644 src/renderer/model/sources/union.js create mode 100644 src/renderer/model/sources/visibilityTracker.js create mode 100644 src/renderer/ol/style/_transform.js create mode 100644 src/renderer/ol/style/dynamic.js diff --git a/src/renderer/components/map/vectorLayers.js b/src/renderer/components/map/vectorLayers.js index 448e2191..3313fce1 100644 --- a/src/renderer/components/map/vectorLayers.js +++ b/src/renderer/components/map/vectorLayers.js @@ -37,6 +37,6 @@ export default (sources, styles) => { // featureLayer: vectorLayer(deselectedSource), featureLayer: vectorLayer(featureSource), highlightLayer: highlightLayer(sources, styles), - selectedLayer: vectorLayer(selectedSource), + selectedLayer: vectorLayer(selectedSource) } } diff --git a/src/renderer/components/map/vectorSources.js b/src/renderer/components/map/vectorSources.js index 91959b36..abac667d 100644 --- a/src/renderer/components/map/vectorSources.js +++ b/src/renderer/components/map/vectorSources.js @@ -1,27 +1,32 @@ -import * as Sources from '../../model/Sources' import * as ID from '../../ids' +import { featureSource } from '../../model/sources/featureSource' +import { highlightTracker } from '../../model/sources/highlightTracker' +import { lockedTracker } from '../../model/sources/lockedTracker' +import { visibilityTracker } from '../../model/sources/visibilityTracker' +import { selectionTracker } from '../../model/sources/selectionTracker' +import { intersect } from '../../model/sources/intersect' +import { union } from '../../model/sources/union' export default async services => { const { store, featureStore, emitter, sessionStore, selection } = services - // const experimentalSource = Sources.experimentalSource(services) - const featureSource = Sources.experimentalSource(services) - // const featureSource = Sources.union( // Sources.featureSource(featureStore, ID.FEATURE_SCOPE), // Sources.featureSource(featureStore, ID.MARKER_SCOPE), // Sources.featureSource(featureStore, ID.MEASURE_SCOPE) // ) - const { visibleSource } = await Sources.visibilityTracker(featureSource, store, emitter) - const { unlockedSource } = Sources.lockedTracker(featureSource, store) + const features = featureSource(services) + + const { visibleSource } = await visibilityTracker(features, store, emitter) + const { unlockedSource } = lockedTracker(features, store) const selectableSource = visibleSource // alias: visible features are selectable - const { selectedSource, deselectedSource } = Sources.selectionTracker(selectableSource, selection) - const highlightSource = Sources.highlightTracker(emitter, store, sessionStore) - const modifiableSource = Sources.intersect(unlockedSource, selectedSource) + const { selectedSource, deselectedSource } = selectionTracker(selectableSource, selection) + const highlightSource = highlightTracker(emitter, store, sessionStore) + const modifiableSource = intersect(unlockedSource, selectedSource) return { - featureSource, + featureSource: features, highlightSource, selectedSource, deselectedSource, diff --git a/src/renderer/model/Sources.js b/src/renderer/model/Sources.js deleted file mode 100644 index f087b528..00000000 --- a/src/renderer/model/Sources.js +++ /dev/null @@ -1,365 +0,0 @@ -import * as R from 'ramda' -import Signal from '@syncpoint/signal' -import Collection from 'ol/Collection' -import VectorSource from 'ol/source/Vector' -import Feature from 'ol/Feature' -import Event from 'ol/events/Event' -import GeoJSON from 'ol/format/GeoJSON' -import * as ID from '../ids' -import styles from '../ol/style/styles' - -const format = new GeoJSON({ - dataProjection: 'EPSG:3857', - featureProjection: 'EPSG:3857' -}) - - -export const experimentalSource = services => { - const { store } = services - const state = { loaded: false } - - const readFeature = source => { - const feature = format.readFeature(source) - const featureId = feature.getId() - const layerId = ID.layerId(featureId) - - feature.$ = { - feature: Signal.of(feature), - globalStyle: Signal.of(state.styles[ID.defaultStyleId]), - layerStyle: Signal.of(state.styles[ID.styleId(layerId)] ?? {}), - featureStyle: Signal.of(state.styles[ID.styleId(featureId)] ?? {}), - centerResolution: Signal.of(state.resolution) - } - - feature.$.styles = styles(feature) - feature.$.styles.on(feature.setStyle.bind(feature)) - - return feature - } - - const reduce = async (prefix, fn, acc) => { - const db = store.db - const it = db.iterator({ gte: `${prefix}`, lte: `${prefix}\xff` }) - for await (const entry of it) acc = fn(acc, entry) - return acc - } - - const push = (acc, [key, value]) => { - acc.push(readFeature(({ id: key, ...value }))) - return acc - } - - const index = new VectorSource({ - useSpatialIndex: true, - features: [] - }) - - const customLoader = async (extent) => { - if (!state.loaded) { - state.loaded = true - // Pre-load all styles for later use. - state.styles = await reduce('style+', (acc, [key, value]) => { - acc[key] = value - return acc - }, {}) - - const features = await reduce(ID.FEATURE_SCOPE, push, []) - index.addFeatures(features) - } - - const loaded = source.getFeatures() - const loading = index.getFeaturesInExtent(extent) - const difference = R.differenceWith((f1, f2) => f1.getId() === f2.getId()) - source.removeFeatures(difference(loaded, loading)) - source.addFeatures(difference(loading, loaded)) - - // Apply resolution to all features currently in view: - source.getFeatures() - .filter(feature => feature.$.resolution) - .forEach(feature => feature.$.resolution(state.resolution)) - } - - // NOTE: Standard bbox strategy only trigger loader if extent increases. - // This way features are never removed. - // See also: https://gis.stackexchange.com/questions/322681/openlayers-refresh-vector-source-with-bbox-strategy-after-map-moved - - const strategy = (extent, resolution) => { - const bbox = extent.join(',') - if (bbox === state.bbox) return [] - state.bbox = bbox - state.resolution = resolution - customLoader(extent) - return [] - } - - const source = new VectorSource({ - features: [], - useSpatialIndex: false, - loader: () => {}, - strategy - }) - - return source -} - -/** - * - */ -export const featureSource = (featureStore, scope) => { - const matchesScope = feature => feature - ? ID.isId(scope)(feature.getId()) - : false - - const source = new VectorSource({ features: [] }) - - featureStore.on('addfeatures', ({ features }) => { - source.addFeatures(features.filter(matchesScope)) - }) - - featureStore.on('removefeatures', ({ features }) => features - .filter(matchesScope) - .forEach(feature => source.removeFeature(feature)) - ) - - return source -} - - -/** - * - */ -export class TouchFeaturesEvent extends Event { - constructor (keys) { - super('touchfeatures') - this.keys = keys - } -} - - -/** - * - */ -export const filter = predicate => source => { - - // Supply empty collection which will be kept in sync with source. - // This is neccessary for interactions which only accept feature collections - // instead of sources (e.g. translate, clone). - const destination = new VectorSource({ features: new Collection() }) - - source.on('addfeature', ({ feature: addition }) => { - if (destination.getFeatureById(addition.getId())) return - if (!predicate(addition.getId())) return - destination.addFeature(addition) - }) - - source.on('removefeature', ({ feature: removal }) => { - const feature = destination.getFeatureById(removal.getId()) - if (feature) destination.removeFeature(feature) - }) - - source.on('touchfeatures', ({ keys }) => { - keys.forEach(key => { - if (predicate(key)) { - // Note: Re-adding would actually remove the feature! - if (destination.getFeatureById(key)) return - const feature = source.getFeatureById(key) - if (feature) destination.addFeature(feature) - } else { - const feature = destination.getFeatureById(key) - if (feature) destination.removeFeature(feature) - } - }) - }) - - const additions = source.getFeatures().filter(feature => predicate(feature.getId())) - destination.addFeatures(additions) - - return destination -} - -/** - * intersect :: ol/source/Vector S => S -> S -> S - */ -export const intersect = (a, b) => { - const intersection = new VectorSource({ features: new Collection() }) - - a.on('addfeature', ({ feature: addition }) => { - if (!b.getFeatureById(addition.getId())) return - intersection.addFeature(addition) - }) - - a.on('removefeature', ({ feature: removal }) => { - const feature = intersection.getFeatureById(removal.getId()) - if (feature) intersection.removeFeature(feature) - }) - - b.on('addfeature', ({ feature: addition }) => { - if (!a.getFeatureById(addition.getId())) return - intersection.addFeature(addition) - }) - - b.on('removefeature', ({ feature: removal }) => { - const feature = intersection.getFeatureById(removal.getId()) - if (feature) intersection.removeFeature(feature) - }) - - return intersection -} - -/** - * union :: ol/source/Vector S => [S] -> S - */ -export const union = (...sources) => { - const union = new VectorSource({ features: new Collection() }) - - sources.forEach(source => { - union.addFeatures(source.getFeatures()) - source.on('addfeature', ({ feature }) => union.addFeature(feature)) - source.on('removefeature', ({ feature }) => union.removeFeature(union.getFeatureById(feature.getId()))) - }) - - return union -} - -/** - * - */ -export const selectionTracker = (source, selection) => { - const keySet = new Set() - - const selected = key => keySet.has(key) - const deselected = key => !keySet.has(key) - - selection.on('selection', ({ selected, deselected }) => { - selected.forEach(key => keySet.add(key)) - deselected.forEach(key => keySet.delete(key)) - const keys = [...selected, ...deselected] - source.dispatchEvent(new TouchFeaturesEvent(keys)) - }) - - return { - selectedSource: filter(selected)(source), - deselectedSource: filter(deselected)(source) - } -} - - -/** - * - */ -export const visibilityTracker = async (source, store, emitter) => { - const keySet = new Set() - const hidden = key => keySet.has(key) - const visible = key => !keySet.has(key) - - await (async () => { - emitter.on('feature/show', ({ ids }) => { - const keys = ids.map(ID.associatedId) - keys.forEach(key => keySet.delete(key)) - source.dispatchEvent(new TouchFeaturesEvent(keys)) - }) - - emitter.on('feature/hide', ({ ids }) => { - const keys = ids.map(ID.associatedId) - keys.forEach(key => keySet.add(key)) - source.dispatchEvent(new TouchFeaturesEvent(keys)) - }) - - store.on('batch', ({ operations }) => { - const candidates = operations - .filter(({ key }) => ID.isHiddenId(key)) - .map(({ type, key }) => ({ type, key: ID.associatedId(key) })) - - const [additions, removals] = R.partition(({ type }) => type === 'put', candidates) - additions.forEach(({ key }) => keySet.add(key)) - removals.forEach(({ key }) => keySet.delete(key)) - - const keys = candidates.map(({ key }) => key) - source.dispatchEvent(new TouchFeaturesEvent(keys)) - }) - - const keys = await store.keys(ID.hiddenId()) - keys.forEach(key => keySet.add(ID.associatedId(key))) - })() - - return { - visibleSource: filter(visible)(source), - hiddenSource: filter(hidden)(source) - } -} - - -/** - * - */ -export const lockedTracker = (source, store) => { - const keySet = new Set() - const locked = key => keySet.has(key) - const unlocked = key => !keySet.has(key) - - ;(async () => { - store.on('batch', ({ operations }) => { - const candidates = operations - .filter(({ key }) => ID.isLockedId(key)) - .map(({ type, key }) => ({ type, key: ID.associatedId(key) })) - - const [additions, removals] = R.partition(({ type }) => type === 'put', candidates) - additions.forEach(({ key }) => keySet.add(key)) - removals.forEach(({ key }) => keySet.delete(key)) - - const keys = candidates.map(({ key }) => key) - source.dispatchEvent(new TouchFeaturesEvent(keys)) - }) - - const keys = await store.keys(ID.lockedId()) - keys.forEach(key => keySet.add(ID.associatedId(key))) - })() - - return { - unlockedSource: filter(unlocked)(source), - lockedSource: filter(locked)(source) - } -} - - -export const highlightTracker = (emitter, store, sessionStore) => { - const source = new VectorSource({ features: new Collection() }) - let timeout - let hiddenIds = [] - - const handleTimeout = () => { - source.clear() - emitter.emit('feature/hide', { ids: hiddenIds }) - } - - emitter.on('highlight/on', async ({ ids }) => { - const viewport = await sessionStore.get('viewport') - const geometries = await store.geometryBounds(ids, viewport.resolution) - const features = geometries.map(geometry => new Feature(geometry)) - source.addFeatures(features) - // Temporarily show hidden feature. - const isHidable = id => ID.isFeatureId(id) || ID.isMarkerId(id) || ID.isMeasureId(id) - - const keys = await store.collectKeys(ids) - const featureIds = keys.filter(isHidable) - const tuples = await store.tuples(featureIds.map(ID.hiddenId)) - - hiddenIds = tuples - .filter(([_, value]) => value) - .map(([key]) => key) - - emitter.emit('feature/show', { ids: hiddenIds }) - - if (timeout) clearTimeout(timeout) - timeout = setTimeout(handleTimeout, 5000) - }) - - emitter.on('highlight/off', () => { - if (!timeout) return - clearTimeout(timeout) - handleTimeout() - timeout = null - }) - - return source -} diff --git a/src/renderer/model/sources/TouchFeaturesEvent.js b/src/renderer/model/sources/TouchFeaturesEvent.js new file mode 100644 index 00000000..03477738 --- /dev/null +++ b/src/renderer/model/sources/TouchFeaturesEvent.js @@ -0,0 +1,11 @@ +import Event from 'ol/events/Event' + +/** + * + */ +export class TouchFeaturesEvent extends Event { + constructor (keys) { + super('touchfeatures') + this.keys = keys + } +} diff --git a/src/renderer/model/sources/__featureSource.js b/src/renderer/model/sources/__featureSource.js new file mode 100644 index 00000000..5b84e928 --- /dev/null +++ b/src/renderer/model/sources/__featureSource.js @@ -0,0 +1,24 @@ +import VectorSource from 'ol/source/Vector' +import * as ID from '../../ids' + +/** + * @deprecated + */ +export const featureSource = (featureStore, scope) => { + const matchesScope = feature => feature + ? ID.isId(scope)(feature.getId()) + : false + + const source = new VectorSource({ features: [] }) + + featureStore.on('addfeatures', ({ features }) => { + source.addFeatures(features.filter(matchesScope)) + }) + + featureStore.on('removefeatures', ({ features }) => features + .filter(matchesScope) + .forEach(feature => source.removeFeature(feature)) + ) + + return source +} diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js new file mode 100644 index 00000000..e8118b43 --- /dev/null +++ b/src/renderer/model/sources/featureSource.js @@ -0,0 +1,150 @@ +import * as R from 'ramda' +import Signal from '@syncpoint/signal' +import VectorSource from 'ol/source/Vector' +import GeoJSON from 'ol/format/GeoJSON' +import * as ID from '../../ids' +import styles from '../../ol/style/styles' +import { flatten, select } from '../../../shared/signal' + +const format = new GeoJSON({ + dataProjection: 'EPSG:3857', + featureProjection: 'EPSG:3857' +}) + +/** + * Read features from GeoJSON to ol/Feature and + * create input signals for style calculation. + */ +const readFeature = R.curry((state, source) => { + const feature = format.readFeature(source) + const featureId = feature.getId() + const layerId = ID.layerId(featureId) + + feature.$ = { + feature: Signal.of(feature), + globalStyle: Signal.of(state.styles[ID.defaultStyleId]), + layerStyle: Signal.of(state.styles[ID.styleId(layerId)] ?? {}), + featureStyle: Signal.of(state.styles[ID.styleId(featureId)] ?? {}), + centerResolution: Signal.of(state.resolution) + } + + feature.$.styles = styles(feature) + feature.$.styles.on(feature.setStyle.bind(feature)) + + return feature +}) + +// Batch operations order: +// 0 - (del, style+) +// 1 - (del, feature) +// 2 - (put, style+) +// 3 - (put, feature) +// 4 - other +const ord = R.cond([ + [R.both(R.propEq('del', 'type'), R.compose(R.startsWith('style+'), R.prop('key'))), R.always(0)], + [R.both(R.propEq('del', 'type'), R.compose(R.startsWith(ID.FEATURE_SCOPE), R.prop('key'))), R.always(1)], + [R.both(R.propEq('put', 'type'), R.compose(R.startsWith('style+'), R.prop('key'))), R.always(2)], + [R.both(R.propEq('put', 'type'), R.compose(R.startsWith(ID.FEATURE_SCOPE), R.prop('key'))), R.always(3)], + [R.T, R.always(4)] +]) + +const isCandidateId = id => ID.isFeatureId(id) || ID.isMarkerId(id) || ID.isMeasureId(id) + +/** + * + */ +export const featureSource = services => { + const { store } = services + const state = { loaded: false } + + // FIXME: Signal should support on/off + store.addEventListener = (type, handler) => store.on(type, handler) + store.removeEventListener = (type, handler) => store.off(type, handler) + + const operations = R.compose( + flatten, + R.map(R.sort((a, b) => ord(a) - ord(b))), + R.map(R.prop('operations')) + )(Signal.fromListeners(['batch'], store)) + + const [ + globalStyle, + layerStyle, + featureStyle, + feature + ] = select([ + R.propEq(ID.defaultStyleId, 'key'), + R.compose(ID.isLayerStyleId, R.prop('key')), + R.compose(ID.isFeatureStyleId, R.prop('key')), + R.compose(isCandidateId, R.prop('key')) + ], operations) + + globalStyle.on(({ value }) => { + indexedSource.getFeatures().forEach(feature => feature.$.globalStyle(value)) + }) + + layerStyle.on(({ type, key, value }) => { + const layerId = ID.layerId(key) + indexedSource.getFeatures() + .filter(feature => ID.layerId(feature.getId()) === layerId) + .forEach(feature => feature.$.layerStyle(type === 'put' ? value : {})) + }) + + featureStyle.on(({ type, key, value }) => { + const feature = indexedSource.getFeatureById(key) + if (feature) feature.$.featureStyle(type === 'put' ? value : {}) + }) + + // Source with spatial index holding all features. + const indexedSource = new VectorSource({ + useSpatialIndex: true, + features: [] + }) + + // Fetch all features and styles on initial load. + const populate = async () => { + state.loaded = true + state.styles = await store.dictionary('style+') + const features = (await store.tuples(ID.FEATURE_SCOPE)) + .map(([id, value]) => ({ id, ...value })) + .map(readFeature(state)) + indexedSource.addFeatures(features) + } + + const loader = async extent => { + if (!state.loaded) await populate() + + // Diff features currently in view and features from extent. + const loaded = source.getFeatures() + const loading = indexedSource.getFeaturesInExtent(extent) + const difference = R.differenceWith((a, b) => a.getId() === b.getId()) + source.removeFeatures(difference(loaded, loading)) + source.addFeatures(difference(loading, loaded)) + + // Apply resolution to all features currently in view: + source.getFeatures() + .filter(feature => feature.$.resolution) + .forEach(feature => feature.$.resolution(state.resolution)) + } + + // Workaround to always trigger loader for updated extent. + // See also: https://gis.stackexchange.com/questions/322681/openlayers-refresh-vector-source-with-bbox-strategy-after-map-moved + + const strategy = (extent, resolution) => { + const bbox = extent.join(',') + if (bbox === state.bbox) return [] + state.bbox = bbox + state.resolution = resolution + loader(extent) + return [] + } + + const source = new VectorSource({ + features: [], + useSpatialIndex: false, + loader: () => {}, + strategy + }) + + return source +} diff --git a/src/renderer/model/sources/filter.js b/src/renderer/model/sources/filter.js new file mode 100644 index 00000000..7c388814 --- /dev/null +++ b/src/renderer/model/sources/filter.js @@ -0,0 +1,43 @@ +import Collection from 'ol/Collection' +import VectorSource from 'ol/source/Vector' + +/** + * + */ +export const filter = predicate => source => { + + // Supply empty collection which will be kept in sync with source. + // This is neccessary for interactions which only accept feature collections + // instead of sources (e.g. translate, clone). + const destination = new VectorSource({ features: new Collection() }) + + source.on('addfeature', ({ feature: addition }) => { + if (destination.getFeatureById(addition.getId())) return + if (!predicate(addition.getId())) return + destination.addFeature(addition) + }) + + source.on('removefeature', ({ feature: removal }) => { + const feature = destination.getFeatureById(removal.getId()) + if (feature) destination.removeFeature(feature) + }) + + source.on('touchfeatures', ({ keys }) => { + keys.forEach(key => { + if (predicate(key)) { + // Note: Re-adding would actually remove the feature! + if (destination.getFeatureById(key)) return + const feature = source.getFeatureById(key) + if (feature) destination.addFeature(feature) + } else { + const feature = destination.getFeatureById(key) + if (feature) destination.removeFeature(feature) + } + }) + }) + + const additions = source.getFeatures().filter(feature => predicate(feature.getId())) + destination.addFeatures(additions) + + return destination +} diff --git a/src/renderer/model/sources/highlightTracker.js b/src/renderer/model/sources/highlightTracker.js new file mode 100644 index 00000000..f0980c78 --- /dev/null +++ b/src/renderer/model/sources/highlightTracker.js @@ -0,0 +1,46 @@ +import Collection from 'ol/Collection' +import VectorSource from 'ol/source/Vector' +import Feature from 'ol/Feature' +import * as ID from '../../ids' + +export const highlightTracker = (emitter, store, sessionStore) => { + const source = new VectorSource({ features: new Collection() }) + let timeout + let hiddenIds = [] + + const handleTimeout = () => { + source.clear() + emitter.emit('feature/hide', { ids: hiddenIds }) + } + + emitter.on('highlight/on', async ({ ids }) => { + const viewport = await sessionStore.get('viewport') + const geometries = await store.geometryBounds(ids, viewport.resolution) + const features = geometries.map(geometry => new Feature(geometry)) + source.addFeatures(features) + // Temporarily show hidden feature. + const isHidable = id => ID.isFeatureId(id) || ID.isMarkerId(id) || ID.isMeasureId(id) + + const keys = await store.collectKeys(ids) + const featureIds = keys.filter(isHidable) + const tuples = await store.tuples(featureIds.map(ID.hiddenId)) + + hiddenIds = tuples + .filter(([_, value]) => value) + .map(([key]) => key) + + emitter.emit('feature/show', { ids: hiddenIds }) + + if (timeout) clearTimeout(timeout) + timeout = setTimeout(handleTimeout, 5000) + }) + + emitter.on('highlight/off', () => { + if (!timeout) return + clearTimeout(timeout) + handleTimeout() + timeout = null + }) + + return source +} diff --git a/src/renderer/model/sources/intersect.js b/src/renderer/model/sources/intersect.js new file mode 100644 index 00000000..8173d5c5 --- /dev/null +++ b/src/renderer/model/sources/intersect.js @@ -0,0 +1,31 @@ +import Collection from 'ol/Collection' +import VectorSource from 'ol/source/Vector' + +/** + * intersect :: ol/source/Vector S => S -> S -> S + */ +export const intersect = (a, b) => { + const intersection = new VectorSource({ features: new Collection() }) + + a.on('addfeature', ({ feature: addition }) => { + if (!b.getFeatureById(addition.getId())) return + intersection.addFeature(addition) + }) + + a.on('removefeature', ({ feature: removal }) => { + const feature = intersection.getFeatureById(removal.getId()) + if (feature) intersection.removeFeature(feature) + }) + + b.on('addfeature', ({ feature: addition }) => { + if (!a.getFeatureById(addition.getId())) return + intersection.addFeature(addition) + }) + + b.on('removefeature', ({ feature: removal }) => { + const feature = intersection.getFeatureById(removal.getId()) + if (feature) intersection.removeFeature(feature) + }) + + return intersection +} diff --git a/src/renderer/model/sources/lockedTracker.js b/src/renderer/model/sources/lockedTracker.js new file mode 100644 index 00000000..9e4213ba --- /dev/null +++ b/src/renderer/model/sources/lockedTracker.js @@ -0,0 +1,36 @@ +import * as R from 'ramda' +import * as ID from '../../ids' +import { TouchFeaturesEvent } from './TouchFeaturesEvent' +import { filter } from './filter' + +/** + * + */ +export const lockedTracker = (source, store) => { + const keySet = new Set() + const locked = key => keySet.has(key) + const unlocked = key => !keySet.has(key) + + ;(async () => { + store.on('batch', ({ operations }) => { + const candidates = operations + .filter(({ key }) => ID.isLockedId(key)) + .map(({ type, key }) => ({ type, key: ID.associatedId(key) })) + + const [additions, removals] = R.partition(({ type }) => type === 'put', candidates) + additions.forEach(({ key }) => keySet.add(key)) + removals.forEach(({ key }) => keySet.delete(key)) + + const keys = candidates.map(({ key }) => key) + source.dispatchEvent(new TouchFeaturesEvent(keys)) + }) + + const keys = await store.keys(ID.lockedId()) + keys.forEach(key => keySet.add(ID.associatedId(key))) + })() + + return { + unlockedSource: filter(unlocked)(source), + lockedSource: filter(locked)(source) + } +} diff --git a/src/renderer/model/sources/selectionTracker.js b/src/renderer/model/sources/selectionTracker.js new file mode 100644 index 00000000..e1132458 --- /dev/null +++ b/src/renderer/model/sources/selectionTracker.js @@ -0,0 +1,24 @@ +import { TouchFeaturesEvent } from './TouchFeaturesEvent' +import { filter } from './filter' + +/** + * + */ +export const selectionTracker = (source, selection) => { + const keySet = new Set() + + const selected = key => keySet.has(key) + const deselected = key => !keySet.has(key) + + selection.on('selection', ({ selected, deselected }) => { + selected.forEach(key => keySet.add(key)) + deselected.forEach(key => keySet.delete(key)) + const keys = [...selected, ...deselected] + source.dispatchEvent(new TouchFeaturesEvent(keys)) + }) + + return { + selectedSource: filter(selected)(source), + deselectedSource: filter(deselected)(source) + } +} diff --git a/src/renderer/model/sources/union.js b/src/renderer/model/sources/union.js new file mode 100644 index 00000000..0e410992 --- /dev/null +++ b/src/renderer/model/sources/union.js @@ -0,0 +1,17 @@ +import Collection from 'ol/Collection' +import VectorSource from 'ol/source/Vector' + +/** + * union :: ol/source/Vector S => [S] -> S + */ +export const union = (...sources) => { + const union = new VectorSource({ features: new Collection() }) + + sources.forEach(source => { + union.addFeatures(source.getFeatures()) + source.on('addfeature', ({ feature }) => union.addFeature(feature)) + source.on('removefeature', ({ feature }) => union.removeFeature(union.getFeatureById(feature.getId()))) + }) + + return union +} diff --git a/src/renderer/model/sources/visibilityTracker.js b/src/renderer/model/sources/visibilityTracker.js new file mode 100644 index 00000000..dc2938d4 --- /dev/null +++ b/src/renderer/model/sources/visibilityTracker.js @@ -0,0 +1,48 @@ +import * as R from 'ramda' +import * as ID from '../../ids' +import { TouchFeaturesEvent } from './TouchFeaturesEvent' +import { filter } from './filter' + +/** + * + */ +export const visibilityTracker = async (source, store, emitter) => { + const keySet = new Set() + const hidden = key => keySet.has(key) + const visible = key => !keySet.has(key) + + await (async () => { + emitter.on('feature/show', ({ ids }) => { + const keys = ids.map(ID.associatedId) + keys.forEach(key => keySet.delete(key)) + source.dispatchEvent(new TouchFeaturesEvent(keys)) + }) + + emitter.on('feature/hide', ({ ids }) => { + const keys = ids.map(ID.associatedId) + keys.forEach(key => keySet.add(key)) + source.dispatchEvent(new TouchFeaturesEvent(keys)) + }) + + store.on('batch', ({ operations }) => { + const candidates = operations + .filter(({ key }) => ID.isHiddenId(key)) + .map(({ type, key }) => ({ type, key: ID.associatedId(key) })) + + const [additions, removals] = R.partition(({ type }) => type === 'put', candidates) + additions.forEach(({ key }) => keySet.add(key)) + removals.forEach(({ key }) => keySet.delete(key)) + + const keys = candidates.map(({ key }) => key) + source.dispatchEvent(new TouchFeaturesEvent(keys)) + }) + + const keys = await store.keys(ID.hiddenId()) + keys.forEach(key => keySet.add(ID.associatedId(key))) + })() + + return { + visibleSource: filter(visible)(source), + hiddenSource: filter(hidden)(source) + } +} diff --git a/src/renderer/ol/style/_transform.js b/src/renderer/ol/style/_transform.js new file mode 100644 index 00000000..874297fc --- /dev/null +++ b/src/renderer/ol/style/_transform.js @@ -0,0 +1,8 @@ +import * as R from 'ramda' +import { transform } from '../../model/geometry' +import { destructure } from '../../../shared/signal' + +export default R.compose( + destructure(['read', 'write', 'pointResolution']), + R.map(transform) +) diff --git a/src/renderer/ol/style/dynamic.js b/src/renderer/ol/style/dynamic.js new file mode 100644 index 00000000..e69de29b diff --git a/src/renderer/ol/style/graphics.js b/src/renderer/ol/style/graphics.js index 8e6398de..bb0c13f4 100644 --- a/src/renderer/ol/style/graphics.js +++ b/src/renderer/ol/style/graphics.js @@ -1,8 +1,17 @@ import Signal from '@syncpoint/signal' +import transform from './_transform' -export default $ => { +export default (geometryType, $) => { + console.log('geometryType', geometryType) $.resolution = Signal.of() + const [read, write, pointResolution] = transform($.geometry) + + $.read = read + $.write = write + $.pointResolution = $.resolution.ap(pointResolution) + + $.pointResolution.on(console.log) return Signal.of(null) -} \ No newline at end of file +} diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index 86b3714e..240089c0 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -23,5 +23,5 @@ export default feature => { const geometryType = Geometry.geometryType(feature.getGeometry()) return geometryType === 'Point' ? point($) - : graphic($) + : graphic(geometryType, $) } From fdf434a0abe66c7f7aaf0882c0ac2289902aab34 Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 4 Jul 2024 10:39:28 +0200 Subject: [PATCH 23/73] deps: updated mocha@10.6.0 --- package-lock.json | 131 ++++++++++++++-------------------------------- package.json | 2 +- 2 files changed, 40 insertions(+), 93 deletions(-) diff --git a/package-lock.json b/package-lock.json index c4103db9..529c1b30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "html-webpack-plugin": "^5.5.0", "jsdoc": "^4.0.0", "memdown": "^6.1.1", - "mocha": "^10.5.2", + "mocha": "^10.6.0", "sass": "^1.77.6", "sass-loader": "^14.2.1", "style-loader": "^4.0.0", @@ -2921,7 +2921,7 @@ }, "node_modules/@syncpoint/signal": { "version": "1.2.0", - "resolved": "git+ssh://git@github.com/syncpoint/signal.git#08cfb29c6bfd715c34fe93744437932db62b20da" + "resolved": "git+ssh://git@github.com/syncpoint/signal.git#6d66285d8b99fe28201352a3ec044158fd6fdf41" }, "node_modules/@syncpoint/signs": { "version": "1.1.0", @@ -3702,9 +3702,9 @@ } }, "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, "engines": { "node": ">=6" @@ -6249,9 +6249,9 @@ "dev": true }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "engines": { "node": ">=0.3.1" @@ -11804,31 +11804,31 @@ } }, "node_modules/mocha": { - "version": "10.5.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.5.2.tgz", - "integrity": "sha512-9btlN3JKCefPf+vKd/kcKz2SXxi12z6JswkGfaAF0saQvnsqLJk504ZmbxhSoENge08E9dsymozKgFMTl5PQsA==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.6.0.tgz", + "integrity": "sha512-hxjt4+EEB0SA0ZDygSS015t65lJw/I2yRCS3Ae+SJ5FrbzrXgfYwJr96f0OvIXdj7h4lv/vLCrH3rkiuizFSvw==", "dev": true, "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", "chokidar": "^3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -11838,29 +11838,6 @@ "node": ">= 14.0.0" } }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -11902,18 +11879,6 @@ "node": ">=8" } }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -11935,15 +11900,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/mousetrap": { "version": "1.6.5", "resolved": "https://registry.npmjs.org/mousetrap/-/mousetrap-1.6.5.tgz", @@ -14613,9 +14569,9 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -15662,15 +15618,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -16814,9 +16761,9 @@ } }, "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, "node_modules/wrap-ansi": { @@ -16927,9 +16874,9 @@ "dev": true }, "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "engines": { "node": ">=10.0.0" diff --git a/package.json b/package.json index c02790ea..d995fe69 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "html-webpack-plugin": "^5.5.0", "jsdoc": "^4.0.0", "memdown": "^6.1.1", - "mocha": "^10.5.2", + "mocha": "^10.6.0", "sass": "^1.77.6", "sass-loader": "^14.2.1", "style-loader": "^4.0.0", From f65b66a0209aae5c3611621e2fa8fbc15923fd77 Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 4 Jul 2024 10:40:46 +0200 Subject: [PATCH 24/73] deps: updated react-tooltip@5.27.1 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 529c1b30..92b52210 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,7 @@ "react-dom": "^18.2.0", "react-easy-sort": "^1.5.1", "react-fast-compare": "^3.2.0", - "react-tooltip": "^5.27.0", + "react-tooltip": "^5.27.1", "reproject": "^1.2.7", "sanitize-filename": "^1.6.3", "subleveldown": "^6.0.1", @@ -13564,9 +13564,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-tooltip": { - "version": "5.27.0", - "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.27.0.tgz", - "integrity": "sha512-JXROcdfCEbCqkAkh8LyTSP3guQ0dG53iY2E2o4fw3D8clKzziMpE6QG6CclDaHELEKTzpMSeAOsdtg0ahoQosw==", + "version": "5.27.1", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.27.1.tgz", + "integrity": "sha512-a+micPXcMOMt11CYlwJD4XShcqGziasHco4NPe1OFw298WBTILMyzUgNC1LAFViAe791JdHNVSJIpzhZm2MvDA==", "dependencies": { "@floating-ui/dom": "^1.6.1", "classnames": "^2.3.0" diff --git a/package.json b/package.json index d995fe69..62c85fa8 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "react-dom": "^18.2.0", "react-easy-sort": "^1.5.1", "react-fast-compare": "^3.2.0", - "react-tooltip": "^5.27.0", + "react-tooltip": "^5.27.1", "reproject": "^1.2.7", "sanitize-filename": "^1.6.3", "subleveldown": "^6.0.1", From b65b07c476c18e622584d658cb6fc381f1f981ac Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 4 Jul 2024 10:55:46 +0200 Subject: [PATCH 25/73] playing with signals (WIP). --- package-lock.json | 2 +- src/renderer/components/map/vectorLayers.js | 4 +- src/renderer/model/sources/featureSource.js | 28 ++++- src/renderer/model/sources/filter.js | 6 +- .../model/sources/visibilityTracker.js | 1 - src/renderer/ol/.gitignore | 1 + src/renderer/ol/style/_evalSync.js | 9 ++ src/renderer/ol/style/_labels.js | 11 ++ src/renderer/ol/style/_placement.js | 35 ++++++ src/renderer/ol/style/_simplifiedGeometry.js | 15 +++ src/renderer/ol/style/_smoothenedGeometry.js | 8 ++ src/renderer/ol/style/chaikin.js | 48 ++++++++ src/renderer/ol/style/defaultStyle.js | 13 ++ src/renderer/ol/style/dynamic.js | 0 src/renderer/ol/style/echelon.js | 53 ++++++++ src/renderer/ol/style/graphics.js | 26 +++- src/renderer/ol/style/graphicsDynamic.js | 14 +++ src/renderer/ol/style/graphicsStatic.js | 6 + src/renderer/ol/style/labels.js | 116 ++++++++++++++++++ .../ol/style/linestring-styles/labels.js | 4 +- .../ol/style/linestring-styles/placement.js | 55 +++++++++ .../ol/style/multipoint-styles/labels.js | 4 +- .../ol/style/polygon-styles/labels.js | 4 +- .../ol/style/polygon-styles/placement.js | 62 ++++++++++ src/renderer/ol/style/styles.js | 1 + 25 files changed, 509 insertions(+), 17 deletions(-) create mode 100644 src/renderer/ol/.gitignore create mode 100644 src/renderer/ol/style/_evalSync.js create mode 100644 src/renderer/ol/style/_labels.js create mode 100644 src/renderer/ol/style/_placement.js create mode 100644 src/renderer/ol/style/_simplifiedGeometry.js create mode 100644 src/renderer/ol/style/_smoothenedGeometry.js create mode 100644 src/renderer/ol/style/chaikin.js create mode 100644 src/renderer/ol/style/defaultStyle.js delete mode 100644 src/renderer/ol/style/dynamic.js create mode 100644 src/renderer/ol/style/echelon.js create mode 100644 src/renderer/ol/style/graphicsDynamic.js create mode 100644 src/renderer/ol/style/graphicsStatic.js create mode 100644 src/renderer/ol/style/labels.js create mode 100644 src/renderer/ol/style/linestring-styles/placement.js create mode 100644 src/renderer/ol/style/polygon-styles/placement.js diff --git a/package-lock.json b/package-lock.json index c4103db9..c35686e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2921,7 +2921,7 @@ }, "node_modules/@syncpoint/signal": { "version": "1.2.0", - "resolved": "git+ssh://git@github.com/syncpoint/signal.git#08cfb29c6bfd715c34fe93744437932db62b20da" + "resolved": "git+ssh://git@github.com/syncpoint/signal.git#6d66285d8b99fe28201352a3ec044158fd6fdf41" }, "node_modules/@syncpoint/signs": { "version": "1.1.0", diff --git a/src/renderer/components/map/vectorLayers.js b/src/renderer/components/map/vectorLayers.js index 3313fce1..ef69055e 100644 --- a/src/renderer/components/map/vectorLayers.js +++ b/src/renderer/components/map/vectorLayers.js @@ -34,8 +34,8 @@ export default (sources, styles) => { }) return { - // featureLayer: vectorLayer(deselectedSource), - featureLayer: vectorLayer(featureSource), + featureLayer: vectorLayer(deselectedSource), + // featureLayer: vectorLayer(featureSource), highlightLayer: highlightLayer(sources, styles), selectedLayer: vectorLayer(selectedSource) } diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index e8118b43..27c172d3 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -5,6 +5,7 @@ import GeoJSON from 'ol/format/GeoJSON' import * as ID from '../../ids' import styles from '../../ol/style/styles' import { flatten, select } from '../../../shared/signal' +import { setCoordinates } from '../geometry' const format = new GeoJSON({ dataProjection: 'EPSG:3857', @@ -31,6 +32,23 @@ const readFeature = R.curry((state, source) => { feature.$.styles = styles(feature) feature.$.styles.on(feature.setStyle.bind(feature)) + // Use dedicated function to update feature coordinates from within + // modify interaction. Such internal changes must not trigger ModifyEvent. + + feature.internalChange = Signal.of(false) + + feature.updateCoordinates = coordinates => { + feature.internalChange(true) + setCoordinates(feature.getGeometry(), coordinates) + feature.internalChange(false) + } + + feature.commit = () => { + // Event must be deferred so that event handler has a chance + // to update to a new state (drag -> selected). + setTimeout(() => feature.dispatchEvent({ type: 'change', target: feature })) + } + return feature }) @@ -57,9 +75,7 @@ export const featureSource = services => { const { store } = services const state = { loaded: false } - // FIXME: Signal should support on/off - store.addEventListener = (type, handler) => store.on(type, handler) - store.removeEventListener = (type, handler) => store.off(type, handler) + // ==> batch event handling const operations = R.compose( flatten, @@ -91,10 +107,13 @@ export const featureSource = services => { }) featureStyle.on(({ type, key, value }) => { - const feature = indexedSource.getFeatureById(key) + const featureId = ID.featureId(key) + const feature = indexedSource.getFeatureById(featureId) if (feature) feature.$.featureStyle(type === 'put' ? value : {}) }) + // <== batch event handling + // Source with spatial index holding all features. const indexedSource = new VectorSource({ useSpatialIndex: true, @@ -134,6 +153,7 @@ export const featureSource = services => { const bbox = extent.join(',') if (bbox === state.bbox) return [] state.bbox = bbox + state.previousResolution = state.resolution state.resolution = resolution loader(extent) return [] diff --git a/src/renderer/model/sources/filter.js b/src/renderer/model/sources/filter.js index 7c388814..d84844f6 100644 --- a/src/renderer/model/sources/filter.js +++ b/src/renderer/model/sources/filter.js @@ -9,7 +9,11 @@ export const filter = predicate => source => { // Supply empty collection which will be kept in sync with source. // This is neccessary for interactions which only accept feature collections // instead of sources (e.g. translate, clone). - const destination = new VectorSource({ features: new Collection() }) + const destination = new VectorSource({ + features: new Collection(), + // Hack to delegate load request upstream: + strategy: (extent, resolution) => source.strategy_(extent, resolution) + }) source.on('addfeature', ({ feature: addition }) => { if (destination.getFeatureById(addition.getId())) return diff --git a/src/renderer/model/sources/visibilityTracker.js b/src/renderer/model/sources/visibilityTracker.js index dc2938d4..ad9e91cf 100644 --- a/src/renderer/model/sources/visibilityTracker.js +++ b/src/renderer/model/sources/visibilityTracker.js @@ -32,7 +32,6 @@ export const visibilityTracker = async (source, store, emitter) => { const [additions, removals] = R.partition(({ type }) => type === 'put', candidates) additions.forEach(({ key }) => keySet.add(key)) removals.forEach(({ key }) => keySet.delete(key)) - const keys = candidates.map(({ key }) => key) source.dispatchEvent(new TouchFeaturesEvent(keys)) }) diff --git a/src/renderer/ol/.gitignore b/src/renderer/ol/.gitignore new file mode 100644 index 00000000..92c252a4 --- /dev/null +++ b/src/renderer/ol/.gitignore @@ -0,0 +1 @@ +style.backup diff --git a/src/renderer/ol/style/_evalSync.js b/src/renderer/ol/style/_evalSync.js new file mode 100644 index 00000000..f8d98635 --- /dev/null +++ b/src/renderer/ol/style/_evalSync.js @@ -0,0 +1,9 @@ +import { echelonCode } from '../../symbology/2525c' +import { echelons } from './echelon' +import { evalSync } from './labels' + +export default (sidc, properties) => { + const sizeCode = echelonCode(sidc) + const echelonText = (sizeCode === '*' || sizeCode === '-') ? '' : echelons[sizeCode]?.text + return evalSync({ modifiers: properties, echelon: echelonText }) +} diff --git a/src/renderer/ol/style/_labels.js b/src/renderer/ol/style/_labels.js new file mode 100644 index 00000000..2a409737 --- /dev/null +++ b/src/renderer/ol/style/_labels.js @@ -0,0 +1,11 @@ +import polygonLabels from './polygon-styles/labels' +import lineStringLabels from './linestring-styles/labels' +import multiPointLabels from './multipoint-styles/labels' + +const LABELS = { + Polygon: polygonLabels, + LineString: lineStringLabels, + MultiPoint: multiPointLabels +} + +export default (geometryType, sidc) => ((LABELS[geometryType] || {})[sidc] || []).flat() diff --git a/src/renderer/ol/style/_placement.js b/src/renderer/ol/style/_placement.js new file mode 100644 index 00000000..294ad535 --- /dev/null +++ b/src/renderer/ol/style/_placement.js @@ -0,0 +1,35 @@ +import * as R from 'ramda' +import * as TS from '../ts' +import Polygon from './polygon-styles/placement' +import LineString from './linestring-styles/placement' + +const PLACEMENT = { + Polygon, + LineString, + MultiPoint: Polygon +} + +const pointBuffer = geometry => { + const [C, A] = TS.coordinates(geometry) + const segment = TS.segment([C, A]) + return TS.pointBuffer(TS.point(C))(segment.getLength()) + +} + +export default $ => { + + if ($.utmSmoothenedGeometry) { + $.utmSmoothenedGeometry.on(console.log) + } + + const placement = $.geometryType.map(geometryType => PLACEMENT[geometryType] || R.identity) + const geometry = $.geometryType.chain(geometryType => { + return R.cond([ + [R.equals('LineString'), R.always($.utmSmoothenedGeometry)], + [R.equals('Polygon'), R.always($.utmSmoothenedGeometry)], + [R.equals('MultiPoint'), R.always($.geometry.ap($.read).map(pointBuffer))] + ])(geometryType) + }) + + return geometry.ap(placement) +} diff --git a/src/renderer/ol/style/_simplifiedGeometry.js b/src/renderer/ol/style/_simplifiedGeometry.js new file mode 100644 index 00000000..5eee0998 --- /dev/null +++ b/src/renderer/ol/style/_simplifiedGeometry.js @@ -0,0 +1,15 @@ + +/** + * + */ +export default (geometry, resolution) => { + const geometryType = geometry.getType() + const coordinates = geometry.getCoordinates() + const simplify = + (geometryType === 'Polygon' && coordinates[0].length > 50) || + (geometryType === 'LineString' && coordinates.length > 50) + + return simplify + ? geometry.simplify(resolution) + : geometry +} diff --git a/src/renderer/ol/style/_smoothenedGeometry.js b/src/renderer/ol/style/_smoothenedGeometry.js new file mode 100644 index 00000000..5f461f8f --- /dev/null +++ b/src/renderer/ol/style/_smoothenedGeometry.js @@ -0,0 +1,8 @@ +import { smooth } from './chaikin' + +/** + * + */ +export default (simplifiedGeometry, lineSmoothing) => lineSmoothing + ? smooth(simplifiedGeometry) + : simplifiedGeometry diff --git a/src/renderer/ol/style/chaikin.js b/src/renderer/ol/style/chaikin.js new file mode 100644 index 00000000..f807ebbd --- /dev/null +++ b/src/renderer/ol/style/chaikin.js @@ -0,0 +1,48 @@ +import * as R from 'ramda' +import { Polygon, LineString, MultiPolygon, MultiLineString, GeometryCollection } from 'ol/geom' + +const lerp = t => (v0, v1) => v0 * (1 - t) + v1 * t +const lerpB = lerp(0.25) +const lerpXY = ([[x1, y1], [x2, y2]]) => [ + [lerpB(x1, x2), lerpB(y1, y2)], + [lerpB(x2, x1), lerpB(y2, y1)] +] + +const chaikinLine = (coords, n) => { + if (n === 0) return coords + + const xs = R.dropLast(1, coords) + .map(([x1, y1], index) => [[x1, y1], coords[index + 1]]) + .flatMap(lerpXY) + + return chaikinLine([R.head(coords), ...xs, R.last(coords)], n - 1) +} + +const chaikinRing = (coords, n) => { + if (n === 0) return coords + + const xs = coords + .map(([x1, y1], index) => [[x1, y1], coords[(index + 1) % coords.length]]) + .flatMap(lerpXY) + + return chaikinRing(xs, n - 1) +} + +const K = v => fn => { fn(v); return v } +const I = v => v + +const closeRing = coords => K(coords)(coords => coords.push(coords[0])) +const smoothRing = n => ring => closeRing(chaikinRing(R.dropLast(1, ring), n)) +const smoothPolygon = n => polygon => polygon.map(smoothRing(n)) +const smoothLine = n => line => chaikinLine(line, n) +const smoothCollection = n => geometry => geometry.getGeometries().map(geometry => smooth(geometry, n)) + +const mappers = n => ({ + Polygon: geometry => new Polygon(geometry.getCoordinates().map(smoothRing(n))), + MultiPolygon: geometry => new MultiPolygon(geometry.getCoordinates().map(smoothPolygon(n))), + LineString: geometry => new LineString(smoothLine(n)(geometry.getCoordinates())), + MultiLineString: geometry => new MultiLineString(geometry.getCoordinates().map(smoothLine(n))), + GeometryCollection: geometry => new GeometryCollection(smoothCollection(n)(geometry)) +}) + +export const smooth = (geometry, n = 3) => (mappers(n)[geometry.getType()] || I)(geometry) diff --git a/src/renderer/ol/style/defaultStyle.js b/src/renderer/ol/style/defaultStyle.js new file mode 100644 index 00000000..9bbd6c24 --- /dev/null +++ b/src/renderer/ol/style/defaultStyle.js @@ -0,0 +1,13 @@ +import { Circle, Fill, Stroke, Style } from 'ol/style' + +export default (options = {}) => { + const fill = new Fill({ color: 'rgba(255,255,255,0.3)' }) + const strokeColor = options.strokeColor ?? '#888' + const stroke = new Stroke({ color: strokeColor, width: 1.25 }) + return new Style({ + geometry: options.geometry, + image: new Circle({ fill, stroke, radius: 5 }), + fill, + stroke + }) +} diff --git a/src/renderer/ol/style/dynamic.js b/src/renderer/ol/style/dynamic.js deleted file mode 100644 index e69de29b..00000000 diff --git a/src/renderer/ol/style/echelon.js b/src/renderer/ol/style/echelon.js new file mode 100644 index 00000000..1bf087b6 --- /dev/null +++ b/src/renderer/ol/style/echelon.js @@ -0,0 +1,53 @@ +import * as R from 'ramda' +import { echelonCode } from '../../symbology/2525c' +import A from './resources/A.png' +import B from './resources/B.png' +import C from './resources/C.png' +import D from './resources/D.png' +import E from './resources/E.png' +import F from './resources/F.png' +import G from './resources/G.png' +import H from './resources/H.png' +import I from './resources/I.png' +import J from './resources/J.png' +import K from './resources/K.png' +import L from './resources/L.png' +import M from './resources/M.png' +import N from './resources/N.png' + +export const echelons = { + A: { width: 24, height: 25, url: A, text: '∅' }, + B: { width: 12, height: 12, url: B, text: '●' }, + C: { width: 26, height: 12, url: C, text: '●●' }, + D: { width: 40, height: 12, url: D, text: '●●●' }, + E: { width: 6, height: 29, url: E, text: '❙' }, + F: { width: 28, height: 29, url: F, text: ' ❙ ❙ ' }, + G: { width: 50, height: 29, url: G, text: ' ❙ ❙ ❙ ' }, + H: { width: 26, height: 29, url: H, text: 'X' }, + I: { width: 64, height: 29, url: I, text: 'X X' }, + J: { width: 101, height: 29, url: J, text: 'X X X' }, + K: { width: 138, height: 29, url: K, text: 'X X X X' }, + L: { width: 176, height: 29, url: L, text: 'X X X X X' }, + M: { width: 213, height: 29, url: M, text: 'X X X X X X' }, + N: { width: 65, height: 24, url: N, text: ' ++ ' } +} + +export default context => { + const { sidc } = context + const code = echelonCode(sidc) + const echelon = echelons[code] + + if (!echelon) return R.identity + + return props => { + const iconImage = props['icon-image'] + if (iconImage !== 'echelon') return props + return { + ...props, + 'icon-height': echelon.height, + 'icon-width': echelon.width, + 'icon-url': echelon.url, + 'icon-scale': 0.4 + } + } +} diff --git a/src/renderer/ol/style/graphics.js b/src/renderer/ol/style/graphics.js index bb0c13f4..2827b695 100644 --- a/src/renderer/ol/style/graphics.js +++ b/src/renderer/ol/style/graphics.js @@ -1,17 +1,33 @@ import Signal from '@syncpoint/signal' import transform from './_transform' +import graphicsStatic from './graphicsStatic' +import graphicsDynamic from './graphicsDynamic' +import { parameterized } from '../../symbology/2525c' -export default (geometryType, $) => { - console.log('geometryType', geometryType) +import labels from './_labels' +import evalSync from './_evalSync' +import placement from './_placement' +export default (geometryType, $) => { $.resolution = Signal.of() const [read, write, pointResolution] = transform($.geometry) - $.read = read $.write = write $.pointResolution = $.resolution.ap(pointResolution) + $.utmGeometry = $.geometry.ap($.read) + $.parameterizedSIDC = $.sidc.map(parameterized) + $.labels = Signal.link(labels, [$.geometryType, $.parameterizedSIDC]) + $.evalSync = Signal.link(evalSync, [$.sidc, $.properties]) + + $.resolution.on(console.log) + + const style = (['LineString', 'Polygon'].includes(geometryType)) + ? graphicsDynamic($) + : graphicsStatic($) + + $.placement = placement($) + // $.placement.on(console.log) - $.pointResolution.on(console.log) - return Signal.of(null) + return style } diff --git a/src/renderer/ol/style/graphicsDynamic.js b/src/renderer/ol/style/graphicsDynamic.js new file mode 100644 index 00000000..58ba3380 --- /dev/null +++ b/src/renderer/ol/style/graphicsDynamic.js @@ -0,0 +1,14 @@ +import Signal from '@syncpoint/signal' +import defaultStyle from './defaultStyle' + +import simplifiedGeometry from './_simplifiedGeometry' +import smoothenedGeometry from './_smoothenedGeometry' + +export default $ => { + $.simplifiedGeometry = Signal.link(simplifiedGeometry, [$.geometry, $.resolution]) + $.lineSmoothing = $.effectiveStyle.map(style => style['line-smooth'] || false) + $.smoothenedGeometry = Signal.link(smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) + $.utmSmoothenedGeometry = $.smoothenedGeometry.ap($.read) + + return $.smoothenedGeometry.map(geometry => defaultStyle({ geometry })) +} diff --git a/src/renderer/ol/style/graphicsStatic.js b/src/renderer/ol/style/graphicsStatic.js new file mode 100644 index 00000000..83fd07fd --- /dev/null +++ b/src/renderer/ol/style/graphicsStatic.js @@ -0,0 +1,6 @@ +import Signal from '@syncpoint/signal' +import defaultStyle from './defaultStyle' + +export default $ => { + return Signal.of(defaultStyle({ strokeColor: 'red' })) +} diff --git a/src/renderer/ol/style/labels.js b/src/renderer/ol/style/labels.js new file mode 100644 index 00000000..f14db774 --- /dev/null +++ b/src/renderer/ol/style/labels.js @@ -0,0 +1,116 @@ +import * as R from 'ramda' +import { Jexl } from 'jexl' +const canvas = document.createElement('canvas') +const context = canvas.getContext('2d') + +/** + * + */ +const textBoundingBox = ({ TS, PI_OVER_2, resolution }, props) => { + const textField = props['text-field'] + if (!textField) return null + if (props['text-clipping'] === 'none') return null + + // Prepare bounding box geometry (dimensions only, including padding). + const lines = textField.split('\n') + const [maxWidthPx, maxHeightPx] = lines.reduce((acc, line) => { + context.font = props['text-font'] + const metrics = context.measureText(line) + const width = metrics.width + const height = 1.2 * lines.length * ((metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent)) + if (width > acc[0]) acc[0] = width + if (height > acc[1]) acc[1] = height + return acc + }, [0, 0]) + + const { x, y } = props.geometry.getCoordinates()[0] + const padding = props['text-padding'] || 0 + const dx = (maxWidthPx / 2 + padding) * resolution + const dy = (maxHeightPx / 2 + padding) * resolution + + const x1 = x - dx + const x2 = x + dx + const y1 = y - dy + const y2 = y + dy + const points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]] + const geometry = TS.polygon(points.map(TS.coordinate)) + + // Transform geometry (rotate/translate) to match + // label options offset, justify and rotate. + + const rotate = props['text-rotate'] || 0 + const justify = props['text-justify'] || 'center' + const [offsetX, offsetY] = props['text-offset'] || [0, 0] + + const flipX = { start: -1, end: 1, center: 0 } + const flipY = rotate < -PI_OVER_2 || rotate > PI_OVER_2 ? -1 : 1 + const tx = (-offsetX + flipX[justify] * (maxWidthPx / 2)) * resolution + const ty = flipY * offsetY * resolution + + const theta = 2 * Math.PI - rotate + const at = TS.AffineTransformation.translationInstance(-(x + tx), -(y + ty)) + at.rotate(theta) + at.translate(x, y) + + return at.transform(geometry) +} + + +/** + * + */ +const iconBoundingBox = ({ TS, resolution }, props) => { + const scale = props['icon-scale'] + if (!scale) return null + + const width = props['icon-width'] * scale / 4 + const height = props['icon-height'] * scale / 4 + const rotate = props['icon-rotate'] || 0 + const padding = props['icon-padding'] || 0 + const { x, y } = props.geometry.getCoordinates()[0] + + const x1 = x - (width + padding) * resolution + const x2 = x + (width + padding) * resolution + const y1 = y - (height + padding) * resolution + const y2 = y + (height + padding) * resolution + const points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]] + const theta = 2 * Math.PI - rotate + const geometry = TS.polygon(points.map(TS.coordinate)) + const rotation = TS.AffineTransformation.rotationInstance(theta, x, y) + return rotation.transform(geometry) +} + + +/** + * + */ +export const boundingBox = R.curry((context, style) => { + if (style['text-field']) return textBoundingBox(context, style) + else if (style['icon-image']) return iconBoundingBox(context, style) + else return null +}) + +const jexl = new Jexl() + +/** + * + */ +export const evalSync = context => { + + const evalSync = textField => Array.isArray(textField) + ? textField.map(evalSync).filter(Boolean).join('\n') + : jexl.evalSync(textField, context) + + return props => { + props = Array.isArray(props) ? props : [props] + return props.reduce((acc, spec) => { + if (!spec['text-field']) acc.push(spec) + else { + const textField = evalSync(spec['text-field']) + if (textField) acc.push({ ...spec, 'text-field': textField }) + } + + return acc + }, []) + } +} diff --git a/src/renderer/ol/style/linestring-styles/labels.js b/src/renderer/ol/style/linestring-styles/labels.js index 06cf3826..6a0538fc 100644 --- a/src/renderer/ol/style/linestring-styles/labels.js +++ b/src/renderer/ol/style/linestring-styles/labels.js @@ -46,7 +46,7 @@ const BND_3 = { id: 'style:default-text', 'text-field': 'echelon', 'text-anchor' const BND = [BND_1, BND_2, BND_3] -export const labels = {} +const labels = {} labels['G*T*A-----'] = [{ 'text-field': 'modifiers.t', 'text-anchor': 0.15, 'text-clipping': 'none' }] // FOLLOW AND ASSUME labels['G*T*AS----'] = [{ 'text-field': 'modifiers.t', 'text-anchor': 0.15, 'text-clipping': 'none' }] // FOLLOW AND SUPPORT labels['G*G*GLB---'] = BND // BOUNDARIES @@ -80,3 +80,5 @@ labels['G*O*BE----'] = MM('"E"') // BEARING LINE / ELECTRONIC labels['G*O*BA----'] = MM('"A"') // BEARING LINE / ACOUSTIC labels['G*O*BT----'] = MM('"T"') // BEARING LINE / TORPEDO labels['G*O*BO----'] = MM('"O"') // BEARING LINE / ELECTRO-OPTICAL INTERCEPT + +export default labels diff --git a/src/renderer/ol/style/linestring-styles/placement.js b/src/renderer/ol/style/linestring-styles/placement.js new file mode 100644 index 00000000..22e2d92a --- /dev/null +++ b/src/renderer/ol/style/linestring-styles/placement.js @@ -0,0 +1,55 @@ +import * as R from 'ramda' +import * as TS from '../../ts' + +/** + * placement :: jts/geom/Geometry => Style => Style + */ +const placement = geometry => { + const segments = TS.segments(geometry) + const line = TS.lengthIndexedLine(geometry) + const endIndex = line.getEndIndex() + const coordAt = (fraction, offset = 0) => line.extractPoint(endIndex * fraction + offset) + const pointAt = (fraction, offset = 0) => TS.point(coordAt(fraction, offset)) + const numPoints = geometry.getNumPoints() + + const segment = fraction => TS.segment([ + coordAt(fraction, -0.05), + coordAt(fraction, +0.05) + ]) + + const angle = anchor => { + if (!anchor) return segment(0.5).angle() + if (isNaN(anchor)) { + if (anchor.includes('center')) return segment(0.5).angle() + else if (anchor.includes('left')) return R.head(segments).angle() + else if (anchor.includes('right')) return R.last(segments).angle() + } else return segment(anchor).angle() + } + + const anchors = anchor => { + if (isNaN(anchor)) { + if (anchor.includes('center')) return pointAt(0.5) + else if (anchor.includes('left')) return geometry.getPointN(0) + else if (anchor.includes('right')) return geometry.getPointN(numPoints - 1) + else return pointAt(0.5) + } else return pointAt(anchor) + } + + const normalize = angle => TS.Angle.normalize(TS.Angle.PI_TIMES_2 - angle) + + return properties => { + const rotate = properties['text-field'] ? 'text-rotate' : 'icon-rotate' + const anchor = properties['text-anchor'] || + properties['icon-anchor'] || + properties['symbol-anchor'] || + (properties['text-field'] ? 'center' : null) + + return { + geometry: anchors(anchor), + ...properties, + [rotate]: normalize(angle(anchor)) + } + } +} + +export default placement diff --git a/src/renderer/ol/style/multipoint-styles/labels.js b/src/renderer/ol/style/multipoint-styles/labels.js index a3668fc9..63160789 100644 --- a/src/renderer/ol/style/multipoint-styles/labels.js +++ b/src/renderer/ol/style/multipoint-styles/labels.js @@ -7,7 +7,7 @@ const ALL_LINES = title => title ? [`"${title}"`, 'modifiers.t', 'modifiers.h', ALT_LINE, DTG_LINE] : ['modifiers.t', 'modifiers.h', ALT_LINE, DTG_LINE] -export const labels = { +const labels = { 'G*F*ATC---': C(ALL_LINES()), // CIRCULAR TARGET 'G*F*ACSC--': C(ALL_LINES('FSA')), // FIRE SUPPORT AREA (FSA) CIRCULAR 'G*F*ACAC--': C(ALL_LINES('ACA')), // AIRSPACE COORDINATION AREA (ACA) CIRCULAR @@ -23,3 +23,5 @@ export const labels = { 'G*F*AKBC--': C(ALL_LINES('BKB'), HALO), // KILL BOX BLUE CIRCULAR 'G*F*AKPC--': C(ALL_LINES('PKB'), HALO) // KILL BOX PURPLE CIRCULAR } + +export default labels diff --git a/src/renderer/ol/style/polygon-styles/labels.js b/src/renderer/ol/style/polygon-styles/labels.js index b397b544..c0e6b31a 100644 --- a/src/renderer/ol/style/polygon-styles/labels.js +++ b/src/renderer/ol/style/polygon-styles/labels.js @@ -18,7 +18,7 @@ const G_G_PM = [ { 'symbol-code': 'GFGPPD----', 'symbol-anchor': 'center', 'symbol-size': 100 } ] -export const labels = {} +const labels = {} labels['G*G*GAG---'] = C(ALL_LINES()) // GENERAL AREA labels['G*G*GAA---'] = C(ALL_LINES('AA')) // ASSEMBLY AREA labels['G*G*GAE---'] = C(ALL_LINES('EA')) // ENGAGEMENT AREA @@ -109,3 +109,5 @@ labels['G*S*ASR---'] = C(ALL_LINES('RSA')) // SUPPORT AREAS / REGIMENTAL (DSA) labels['G*M*NR----'] = [{ 'symbol-code': 'GFMPNZ----', 'symbol-anchor': 'center' }] // RADIOACTIVE AREA labels['G*M*NB----'] = [{ 'symbol-code': 'GFMPNEB---', 'symbol-anchor': 'center' }] // BIOLOGICALLY CONTAMINATED AREA labels['G*M*NC----'] = [{ 'symbol-code': 'GFMPNEC---', 'symbol-anchor': 'center' }] // CHEMICALLY CONTAMINATED AREA + +export default labels diff --git a/src/renderer/ol/style/polygon-styles/placement.js b/src/renderer/ol/style/polygon-styles/placement.js new file mode 100644 index 00000000..e27d8fc3 --- /dev/null +++ b/src/renderer/ol/style/polygon-styles/placement.js @@ -0,0 +1,62 @@ +import * as TS from '../../ts' + +const lazy = function (fn) { + let evaluated = false + let value + + return function () { + if (evaluated) return value + value = fn.apply(this, arguments) + evaluated = true + return value + } +} + +/** + * placement :: jts/geom/Geometry => Style => Style + */ +const placement = geometry => { + const ring = geometry.getExteriorRing() + const envelope = ring.getEnvelopeInternal() + const centroid = TS.centroid(ring) + const [minX, maxX] = [envelope.getMinX(), envelope.getMaxX()] + const [minY, maxY] = [envelope.getMinY(), envelope.getMaxY()] + + const xIntersection = lazy(() => { + const coord = x => TS.coordinate(x, centroid.y) + const axis = TS.lineString([minX, maxX].map(coord)) + return TS.intersection([geometry, axis]).getCoordinates() + }) + + const yIntersection = lazy(() => { + const coord = y => TS.coordinate(centroid.x, y) + const axis = TS.lineString([minY, maxY].map(coord)) + return TS.intersection([geometry, axis]).getCoordinates() + }) + + const fraction = factor => { + const lengthIndexedLine = TS.lengthIndexedLine(ring) + const length = lengthIndexedLine.getEndIndex() + const coord = lengthIndexedLine.extractPoint(factor * length) + return TS.point(coord) + } + + const anchors = { + center: lazy(() => TS.point(centroid)), + bottom: lazy(() => TS.point(yIntersection()[0])), + top: lazy(() => TS.point(yIntersection()[1])), + left: lazy(() => TS.point(xIntersection()[0])), + right: lazy(() => TS.point(xIntersection()[1])) + } + + return properties => { + const anchor = properties['text-anchor'] + const geometry = Number.isFinite(anchor) + ? fraction(anchor) + : anchors[anchor || 'center']() + + return { geometry, ...properties } + } +} + +export default placement diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index 240089c0..723b50d3 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -14,6 +14,7 @@ export default feature => { $.properties = $.feature.map(feature => feature.getProperties()) $.geometry = $.properties.map(({ geometry }) => geometry) + $.geometryType = $.geometry.map(geometry => Geometry.geometryType(geometry)) $.sidc = $.properties.map(R.prop('sidc')) $.colorScheme = Signal.link(colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) $.schemeStyle = Signal.link(schemeStyle, [$.sidc, $.colorScheme]) From 1a0ce5142bf289e1a36a62f48dc137be1ce18941 Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 4 Jul 2024 12:03:54 +0200 Subject: [PATCH 26/73] deps: updated rbush@4.0.0 --- package-lock.json | 16 ++++++++++++---- package.json | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 92b52210..c7e31d33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,7 @@ "path-to-regexp": "^6.2.1", "proj4": "^2.8.0", "ramda": "^0.30.1", - "rbush": "^3.0.1", + "rbush": "^4.0.0", "react": "^18.2.0", "react-cool-virtual": "^0.7.0", "react-dom": "^18.2.0", @@ -12646,6 +12646,14 @@ "url": "https://opencollective.com/openlayers" } }, + "node_modules/ol/node_modules/rbush": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", + "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "dependencies": { + "quickselect": "^2.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -13483,9 +13491,9 @@ } }, "node_modules/rbush": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz", - "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rbush/-/rbush-4.0.0.tgz", + "integrity": "sha512-F5xw+166FYDZI6jEcz+sWEHL5/J+du3kQWkwqWrPKb6iVoLPZh+2KhTS4OoYqrw1v/RO1xQe6WsLwBvrUAlvXw==", "dependencies": { "quickselect": "^2.0.0" } diff --git a/package.json b/package.json index 62c85fa8..087bef92 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "path-to-regexp": "^6.2.1", "proj4": "^2.8.0", "ramda": "^0.30.1", - "rbush": "^3.0.1", + "rbush": "^4.0.0", "react": "^18.2.0", "react-cool-virtual": "^0.7.0", "react-dom": "^18.2.0", From 38ea7c95f700860b857ce8ed93605cfc761f8d33 Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 4 Jul 2024 12:07:51 +0200 Subject: [PATCH 27/73] deps: updated path-to-regexp@7.0.0 --- package-lock.json | 11 +++++++---- package.json | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index c7e31d33..0060a333 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", - "path-to-regexp": "^6.2.1", + "path-to-regexp": "^7.0.0", "proj4": "^2.8.0", "ramda": "^0.30.1", "rbush": "^4.0.0", @@ -12964,9 +12964,12 @@ } }, "node_modules/path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-7.0.0.tgz", + "integrity": "sha512-58Y94bQqF3zBIASFNiufRPH1NfgZth1qwZ35radL87sg8pgbVqr6uikAhqZtFD+w65MGH6SWnY/ly3GbrM4fbg==", + "engines": { + "node": ">=16" + } }, "node_modules/pause-stream": { "version": "0.0.11", diff --git a/package.json b/package.json index 087bef92..e6af6c03 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", - "path-to-regexp": "^6.2.1", + "path-to-regexp": "^7.0.0", "proj4": "^2.8.0", "ramda": "^0.30.1", "rbush": "^4.0.0", From 9df0e85204d845344e068f4f84a3b412739ec245 Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 4 Jul 2024 12:15:06 +0200 Subject: [PATCH 28/73] deps: updated fuse.js@7.0.0 --- package-lock.json | 16 ++++++++++++---- package.json | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0060a333..12f56df9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", "color": "^4.2.3", - "fuse.js": "^6.6.2", + "fuse.js": "^7.0.0", "geo-coordinates-parser": "^1.7.3", "geodesy": "^2.4.0", "jexl": "^2.3.0", @@ -8609,9 +8609,9 @@ } }, "node_modules/fuse.js": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz", - "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz", + "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==", "engines": { "node": ">=10" } @@ -10833,6 +10833,14 @@ "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/kbar/node_modules/fuse.js": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-6.6.2.tgz", + "integrity": "sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==", + "engines": { + "node": ">=10" + } + }, "node_modules/kbar/node_modules/react-virtual": { "version": "2.10.4", "resolved": "https://registry.npmjs.org/react-virtual/-/react-virtual-2.10.4.tgz", diff --git a/package.json b/package.json index e6af6c03..3a49a082 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,7 @@ "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", "color": "^4.2.3", - "fuse.js": "^6.6.2", + "fuse.js": "^7.0.0", "geo-coordinates-parser": "^1.7.3", "geodesy": "^2.4.0", "jexl": "^2.3.0", From 2c0e80a1f33bd5062aae8361889a54c88f8e86b4 Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 4 Jul 2024 12:16:16 +0200 Subject: [PATCH 29/73] deps: removed eslint --- package-lock.json | 87 ++++++++++++++++++++++++++++++++++++++++------- package.json | 1 - 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12f56df9..5c0d23ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,6 @@ "electron": "^26.6.0", "electron-builder": "^24.6.4", "electron-updater": "^6.1.4", - "eslint": "^8.57.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.0", @@ -2197,6 +2196,7 @@ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, + "peer": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -2212,6 +2212,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2224,6 +2225,7 @@ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", "dev": true, + "peer": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -2233,6 +2235,7 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "peer": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -2256,6 +2259,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2266,6 +2270,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -2281,6 +2286,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2293,6 +2299,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -2305,6 +2312,7 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -2345,6 +2353,7 @@ "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", "deprecated": "Use @eslint/config-array instead", "dev": true, + "peer": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", "debug": "^4.3.1", @@ -2359,6 +2368,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2369,6 +2379,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2381,6 +2392,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "peer": true, "engines": { "node": ">=12.22" }, @@ -2394,7 +2406,8 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", - "dev": true + "dev": true, + "peer": true }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -2731,6 +2744,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "peer": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -2744,6 +2758,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "peer": true, "engines": { "node": ">= 8" } @@ -2753,6 +2768,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "peer": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -3310,7 +3326,8 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", @@ -3592,6 +3609,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "peer": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -5115,6 +5133,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "peer": true, "engines": { "node": ">=6" } @@ -6092,7 +6111,8 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/default-browser": { "version": "5.2.1", @@ -6384,6 +6404,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "peer": true, "dependencies": { "esutils": "^2.0.2" }, @@ -7292,6 +7313,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -7793,6 +7815,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "peer": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -7808,6 +7831,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7818,6 +7842,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7834,6 +7859,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "peer": true, "dependencies": { "color-name": "~1.1.4" }, @@ -7845,13 +7871,15 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "peer": true }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -7864,6 +7892,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -7880,6 +7909,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -7892,6 +7922,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "peer": true, "dependencies": { "type-fest": "^0.20.2" }, @@ -7907,6 +7938,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -7916,6 +7948,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -7928,6 +7961,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -7940,6 +7974,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "peer": true, "engines": { "node": ">=10" }, @@ -7952,6 +7987,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "peer": true, "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -7969,6 +8005,7 @@ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, + "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -7981,6 +8018,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, + "peer": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -8205,7 +8243,8 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/fastest-levenshtein": { "version": "1.0.16", @@ -8226,6 +8265,7 @@ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", "dev": true, + "peer": true, "dependencies": { "reusify": "^1.0.4" } @@ -8261,6 +8301,7 @@ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, + "peer": true, "dependencies": { "flat-cache": "^3.0.4" }, @@ -8404,6 +8445,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, + "peer": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -8417,7 +8459,8 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/follow-redirects": { "version": "1.15.6", @@ -8822,6 +8865,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "peer": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -9025,7 +9069,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "peer": true }, "node_modules/handle-thing": { "version": "2.0.1", @@ -9563,6 +9608,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, + "peer": true, "engines": { "node": ">= 4" } @@ -9578,6 +9624,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "peer": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -9677,6 +9724,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "peer": true, "engines": { "node": ">=0.8.19" } @@ -10109,6 +10157,7 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, + "peer": true, "engines": { "node": ">=8" } @@ -10721,7 +10770,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/json-stringify-safe": { "version": "5.0.1", @@ -11049,6 +11099,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "peer": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -11168,7 +11219,8 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "peer": true }, "node_modules/lodash.union": { "version": "4.6.0", @@ -11975,7 +12027,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/negotiator": { "version": "0.6.3", @@ -12730,6 +12783,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "peer": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -12853,6 +12907,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "peer": true, "dependencies": { "callsites": "^3.0.0" }, @@ -13254,6 +13309,7 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "peer": true, "engines": { "node": ">= 0.8.0" } @@ -14053,6 +14109,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "peer": true, "engines": { "node": ">=4" } @@ -14101,6 +14158,7 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", "dev": true, + "peer": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -14121,6 +14179,7 @@ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, + "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -14180,6 +14239,7 @@ "url": "https://feross.org/support" } ], + "peer": true, "dependencies": { "queue-microtask": "^1.2.2" } @@ -15716,7 +15776,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "peer": true }, "node_modules/thingies": { "version": "1.21.0", @@ -15888,6 +15949,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "peer": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -16775,6 +16837,7 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } diff --git a/package.json b/package.json index 3a49a082..17e409f3 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "electron": "^26.6.0", "electron-builder": "^24.6.4", "electron-updater": "^6.1.4", - "eslint": "^8.57.0", "eslint-config-standard": "^17.0.0", "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.0", From c153658f8babcbc07a3fbd328c08f9215ff23595 Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 4 Jul 2024 12:28:53 +0200 Subject: [PATCH 30/73] deps: updated electron@31.1.0 --- electron-builder.yml | 2 +- package-lock.json | 16 ++++++++-------- package.json | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/electron-builder.yml b/electron-builder.yml index 75c5fe00..08299f49 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -35,4 +35,4 @@ publish: - provider: github releaseType: release -electronVersion: 26.6.0 +electronVersion: 31.1.0 diff --git a/package-lock.json b/package-lock.json index 5c0d23ca..ce3e406e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "babel-loader": "^9.1.0", "c8": "^10.1.2", "css-loader": "^7.1.2", - "electron": "^26.6.0", + "electron": "^31.1.0", "electron-builder": "^24.6.4", "electron-updater": "^6.1.4", "eslint-config-standard": "^17.0.0", @@ -6554,14 +6554,14 @@ } }, "node_modules/electron": { - "version": "26.6.10", - "resolved": "https://registry.npmjs.org/electron/-/electron-26.6.10.tgz", - "integrity": "sha512-pV2SD0RXzAiNRb/2yZrsVmVkBOMrf+DVsPulIgRjlL0+My9BL5spFuhHVMQO9yHl9tFpWtuRpQv0ofM/i9P8xg==", + "version": "31.1.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-31.1.0.tgz", + "integrity": "sha512-TBOwqLxSxnx6+pH6GMri7R3JPH2AkuGJHfWZS0p1HsmN+Qr1T9b0IRJnnehSd/3NZAmAre4ft9Ljec7zjyKFJA==", "dev": true, "hasInstallScript": true, "dependencies": { "@electron/get": "^2.0.0", - "@types/node": "^18.11.18", + "@types/node": "^20.9.0", "extract-zip": "^2.0.1" }, "bin": { @@ -6984,9 +6984,9 @@ } }, "node_modules/electron/node_modules/@types/node": { - "version": "18.19.34", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", - "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", + "version": "20.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", + "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" diff --git a/package.json b/package.json index 17e409f3..474f0d4f 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "babel-loader": "^9.1.0", "c8": "^10.1.2", "css-loader": "^7.1.2", - "electron": "^26.6.0", + "electron": "^31.1.0", "electron-builder": "^24.6.4", "electron-updater": "^6.1.4", "eslint-config-standard": "^17.0.0", From 3bf11750609e59acafc3da0d61abcf02954eccf4 Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 4 Jul 2024 15:15:09 +0200 Subject: [PATCH 31/73] playing with signals (WIP). --- package-lock.json | 11 +- package.json | 2 +- src/renderer/ol/style/_placement.js | 5 - src/renderer/ol/style/graphics.js | 33 ---- src/renderer/ol/style/graphicsDynamic.js | 14 -- src/renderer/ol/style/graphicsStatic.js | 6 - src/renderer/ol/style/polygon.js | 36 ++++ src/renderer/ol/style/styles.js | 10 +- src/renderer/store/FeatureStore.js | 209 ----------------------- 9 files changed, 49 insertions(+), 277 deletions(-) delete mode 100644 src/renderer/ol/style/graphics.js delete mode 100644 src/renderer/ol/style/graphicsDynamic.js delete mode 100644 src/renderer/ol/style/graphicsStatic.js create mode 100644 src/renderer/ol/style/polygon.js diff --git a/package-lock.json b/package-lock.json index 92b52210..25191410 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", - "path-to-regexp": "^6.2.1", + "path-to-regexp": "^7.0.0", "proj4": "^2.8.0", "ramda": "^0.30.1", "rbush": "^3.0.1", @@ -12956,9 +12956,12 @@ } }, "node_modules/path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-7.0.0.tgz", + "integrity": "sha512-58Y94bQqF3zBIASFNiufRPH1NfgZth1qwZ35radL87sg8pgbVqr6uikAhqZtFD+w65MGH6SWnY/ly3GbrM4fbg==", + "engines": { + "node": ">=16" + } }, "node_modules/pause-stream": { "version": "0.0.11", diff --git a/package.json b/package.json index 62c85fa8..dc77f6ec 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", - "path-to-regexp": "^6.2.1", + "path-to-regexp": "^7.0.0", "proj4": "^2.8.0", "ramda": "^0.30.1", "rbush": "^3.0.1", diff --git a/src/renderer/ol/style/_placement.js b/src/renderer/ol/style/_placement.js index 294ad535..7456a557 100644 --- a/src/renderer/ol/style/_placement.js +++ b/src/renderer/ol/style/_placement.js @@ -17,11 +17,6 @@ const pointBuffer = geometry => { } export default $ => { - - if ($.utmSmoothenedGeometry) { - $.utmSmoothenedGeometry.on(console.log) - } - const placement = $.geometryType.map(geometryType => PLACEMENT[geometryType] || R.identity) const geometry = $.geometryType.chain(geometryType => { return R.cond([ diff --git a/src/renderer/ol/style/graphics.js b/src/renderer/ol/style/graphics.js deleted file mode 100644 index 2827b695..00000000 --- a/src/renderer/ol/style/graphics.js +++ /dev/null @@ -1,33 +0,0 @@ -import Signal from '@syncpoint/signal' -import transform from './_transform' -import graphicsStatic from './graphicsStatic' -import graphicsDynamic from './graphicsDynamic' -import { parameterized } from '../../symbology/2525c' - -import labels from './_labels' -import evalSync from './_evalSync' -import placement from './_placement' - -export default (geometryType, $) => { - $.resolution = Signal.of() - const [read, write, pointResolution] = transform($.geometry) - $.read = read - $.write = write - $.pointResolution = $.resolution.ap(pointResolution) - $.utmGeometry = $.geometry.ap($.read) - $.parameterizedSIDC = $.sidc.map(parameterized) - $.labels = Signal.link(labels, [$.geometryType, $.parameterizedSIDC]) - $.evalSync = Signal.link(evalSync, [$.sidc, $.properties]) - - $.resolution.on(console.log) - - const style = (['LineString', 'Polygon'].includes(geometryType)) - ? graphicsDynamic($) - : graphicsStatic($) - - $.placement = placement($) - // $.placement.on(console.log) - - - return style -} diff --git a/src/renderer/ol/style/graphicsDynamic.js b/src/renderer/ol/style/graphicsDynamic.js deleted file mode 100644 index 58ba3380..00000000 --- a/src/renderer/ol/style/graphicsDynamic.js +++ /dev/null @@ -1,14 +0,0 @@ -import Signal from '@syncpoint/signal' -import defaultStyle from './defaultStyle' - -import simplifiedGeometry from './_simplifiedGeometry' -import smoothenedGeometry from './_smoothenedGeometry' - -export default $ => { - $.simplifiedGeometry = Signal.link(simplifiedGeometry, [$.geometry, $.resolution]) - $.lineSmoothing = $.effectiveStyle.map(style => style['line-smooth'] || false) - $.smoothenedGeometry = Signal.link(smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) - $.utmSmoothenedGeometry = $.smoothenedGeometry.ap($.read) - - return $.smoothenedGeometry.map(geometry => defaultStyle({ geometry })) -} diff --git a/src/renderer/ol/style/graphicsStatic.js b/src/renderer/ol/style/graphicsStatic.js deleted file mode 100644 index 83fd07fd..00000000 --- a/src/renderer/ol/style/graphicsStatic.js +++ /dev/null @@ -1,6 +0,0 @@ -import Signal from '@syncpoint/signal' -import defaultStyle from './defaultStyle' - -export default $ => { - return Signal.of(defaultStyle({ strokeColor: 'red' })) -} diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js new file mode 100644 index 00000000..884a6997 --- /dev/null +++ b/src/renderer/ol/style/polygon.js @@ -0,0 +1,36 @@ +import Signal from '@syncpoint/signal' +import transform from './_transform' +import { parameterized } from '../../symbology/2525c' +import polygonLabels from './polygon-styles/labels' +import placement from './polygon-styles/placement' +import evalSync from './_evalSync' +import smoothenedGeometry from './_smoothenedGeometry' +import defaultStyle from './defaultStyle' + +const labels = sidc => (polygonLabels[sidc] || []).flat() +const lineSmoothing = style => style['line-smooth'] || false +const simplifiedGeometry = (geometry, resolution) => { + const coordinates = geometry.getCoordinates() + return coordinates[0].length > 50 + ? geometry.simplify(resolution) + : geometry +} + +export default $ => { + $.resolution = Signal.of() + const [read, write, pointResolution] = transform($.geometry) + $.read = read + $.write = write + $.pointResolution = $.resolution.ap(pointResolution) + $.utmGeometry = $.geometry.ap($.read) + $.parameterizedSIDC = $.sidc.map(parameterized) + $.labels = $.parameterizedSIDC.map(labels) + $.evalSync = Signal.link(evalSync, [$.sidc, $.properties]) + $.simplifiedGeometry = Signal.link(simplifiedGeometry, [$.geometry, $.resolution]) + $.lineSmoothing = $.effectiveStyle.map(lineSmoothing) + $.smoothenedGeometry = Signal.link(smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) + $.utmSmoothenedGeometry = $.smoothenedGeometry.ap($.read) + $.placement = $.utmSmoothenedGeometry.map(placement) + + return $.smoothenedGeometry.map(geometry => defaultStyle({ geometry })) +} diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index 723b50d3..fc5591e8 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -3,7 +3,8 @@ import Signal from '@syncpoint/signal' import * as Geometry from '../../model/geometry' import styleRegistry from './styleRegistry' import point from './point' -import graphic from './graphics' +import polygon from './polygon' +import defaultStyle from './defaultStyle' import colorScheme from './_colorScheme' import schemeStyle from './_schemeStyle' @@ -14,7 +15,6 @@ export default feature => { $.properties = $.feature.map(feature => feature.getProperties()) $.geometry = $.properties.map(({ geometry }) => geometry) - $.geometryType = $.geometry.map(geometry => Geometry.geometryType(geometry)) $.sidc = $.properties.map(R.prop('sidc')) $.colorScheme = Signal.link(colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) $.schemeStyle = Signal.link(schemeStyle, [$.sidc, $.colorScheme]) @@ -22,7 +22,7 @@ export default feature => { $.styleRegistry = $.effectiveStyle.map(styleRegistry) const geometryType = Geometry.geometryType(feature.getGeometry()) - return geometryType === 'Point' - ? point($) - : graphic(geometryType, $) + if (geometryType === 'Point') return point($) + else if (geometryType === 'Polygon') return polygon($) + else return Signal.of(defaultStyle()) } diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index 463836c7..5e0513d1 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -1,15 +1,8 @@ /* eslint-disable camelcase */ -import * as R from 'ramda' import util from 'util' import GeoJSON from 'ol/format/GeoJSON' import * as Extent from 'ol/extent' -import Signal from '@syncpoint/signal' import Emitter from '../../shared/emitter' -import { setCoordinates } from '../model/geometry' -import { flatten, select, once } from '../../shared/signal' -import * as ID from '../ids' -import style from '../ol/style/styles' - const format = new GeoJSON({ dataProjection: 'EPSG:3857', @@ -29,36 +22,6 @@ export const writeGeometryObject = geometry => format.writeGeometryObject(geomet export const writeFeatureCollection = features => format.writeFeaturesObject(features) export const writeFeatureObject = feature => format.writeFeatureObject(feature) -const isCandidateId = id => ID.isFeatureId(id) || ID.isMarkerId(id) || ID.isMeasureId(id) - -const isGeometry = value => { - if (!value) return false - else if (typeof value !== 'object') return false - else { - if (!value.type) return false - else if (!value.coordinates && !value.geometries) return false - return true - } -} - -// Batch operations order: -// 0 - (del, style+) -// 1 - (del, feature) -// 2 - (put, feature) -// 3 - (put, style+) -const ord = R.cond([ - [R.both(R.propEq('del', 'type'), R.compose(R.startsWith('style+'), R.prop('key'))), R.always(0)], - [R.both(R.propEq('del', 'type'), R.compose(R.startsWith(ID.FEATURE_SCOPE), R.prop('key'))), R.always(1)], - [R.both(R.propEq('put', 'type'), R.compose(R.startsWith(ID.FEATURE_SCOPE), R.prop('key'))), R.always(2)], - [R.both(R.propEq('del', 'type'), R.compose(R.startsWith('style+'), R.prop('key'))), R.always(3)], - [R.T, R.always(4)] -]) - -const push = (acc, [key, value]) => { - acc.push({ type: 'put', key, value }) - return acc -} - /** * */ @@ -68,129 +31,6 @@ export function FeatureStore (store, selection, emitter) { this.emitter = emitter this.features = {} this.styleProperties = {} // global (default), layer and feature style properties - - // FIXME: Signal should support on/off - store.addEventListener = (type, handler) => store.on(type, handler) - store.removeEventListener = (type, handler) => store.off(type, handler) - selection.addEventListener = (type, handler) => selection.on(type, handler) - selection.removeEventListener = (type, handler) => selection.off(type, handler) - emitter.addEventListener = (type, handler) => emitter.on(type, handler) - emitter.removeEventListener = (type, handler) => emitter.off(type, handler) - - const operations = R.compose( - flatten, - R.map(R.sort((a, b) => ord(a) - ord(b))), - R.map(R.prop('operations')) - )(Signal.fromListeners(['batch'], store)) - - const [ - globalStyle, - featureStyle, - layerStyle, - feature - ] = select([ - R.propEq(ID.defaultStyleId, 'key'), - R.compose(ID.isFeatureStyleId, R.prop('key')), - R.compose(ID.isLayerStyleId, R.prop('key')), - R.compose(isCandidateId, R.prop('key')) - ], operations) - - globalStyle.on(({ value }) => { - Object.values(this.features).forEach(feature => feature.$.globalStyle(value)) - }) - - featureStyle.on(({ type, key, value }) => { - const feature = this.features[ID.featureId(key)] - if (feature) feature.$.featureStyle(type === 'put' ? value : {}) - }) - - layerStyle.on(({ type, key, value }) => { - const layerId = ID.layerId(key) - Object.entries(this.features) - .filter(([key]) => ID.layerId(key) === layerId) - .forEach(([, feature]) => feature.$.layerStyle(type === 'put' ? value : {})) - }) - - const [ - featureRemoval, - featureUpdate, - featureAddition - ] = select([ - R.propEq('del', 'type'), - ({ key }) => this.features[key], - R.T - ], feature) - - const isValid = feature => feature?.type === 'Feature' && feature.geometry - - const trim = properties => { - const { geometry, ...rest } = properties - return geometry - ? properties - : rest - } - - featureRemoval.on(({ key }) => { - const features = [this.features[key]] - delete this.features[key] - this.emit('removefeatures', ({ features })) - }) - - featureAddition - .map(({ key, value }) => ({ id: key, ...value })) - .map(readFeature) - // .filter(feature => Geometry.geometryType(feature) === 'Polygon') - // .filter(feature => feature.getGeometry().getCoordinates()[0].length > 80) - // .filter(feature => feature.getProperties().sidc.startsWith('M')) - .map(this.wrapFeature.bind(this)) - .on(feature => this.features[feature.getId()] = feature) - - featureUpdate.on(({ key, value }) => { - const properties = isGeometry(value) - ? { geometry: readGeometry(value) } - : trim(readFeature(value).getProperties()) - - const feature = this.features[key] - feature.setProperties({ ...feature.getProperties(), ...properties }) - }) - - // TODO: limit to features - const $selection = Signal.fromListeners(['selection'], selection) - const modes = ({ deselected }) => { - const selected = selection.selected() - const mode = selected.length > 1 ? 'multiselect' : 'singleselect' - return [ - ...deselected.map(key => [key, 'default']), - ...selected.map(key => [key, mode]) - ] - } - - const $mode = R.compose( - flatten, - R.map(modes) - )($selection) - - // $mode.on(console.log) - - // selection.on('selection', ({ deselected, selected }) => { - // deselected.forEach(key => { - // if (this.features[key]) this.features[key].apply({ mode: 'default' }) - // }) - - // const mode = selection.selected().length > 1 - // ? 'multiselect' - // : 'singleselect' - - // selected.forEach(key => { - // if (this.features[key]) this.features[key].apply({ mode }) - // }) - // }) - - const centerResolution = Signal.fromListeners(['view/resolution'], emitter) - centerResolution.on(({ resolution }) => { - this.resolution = resolution - Object.values(this.features).forEach(feature => feature.$.centerResolution(resolution)) - }) } util.inherits(FeatureStore, Emitter) @@ -199,21 +39,6 @@ util.inherits(FeatureStore, Emitter) * */ FeatureStore.prototype.bootstrap = async function () { - - // pre-load and store global style - this.globalStyle = await this.store.value(ID.defaultStyleId) - - const reduce = async (prefix, fn, acc) => { - const db = this.store.db - const it = db.iterator({ gte: `${prefix}`, lte: `${prefix}\xff` }) - for await (const entry of it) acc = fn(acc, entry) - return acc - } - - // styles: Only needed while loading features. - const operations = await reduce('style+', push, []) - await reduce(ID.FEATURE_SCOPE, push, operations) - this.store.emit('batch', { operations }) } @@ -241,38 +66,4 @@ FeatureStore.prototype.feature = function (key) { * */ FeatureStore.prototype.wrapFeature = function (feature) { - const featureId = feature.getId() - const layerId = ID.layerId(featureId) - - feature.$ = { - feature: Signal.of(feature), - globalStyle: Signal.of(this.globalStyle), - layerStyle: Signal.of({}), - featureStyle: Signal.of({}), - centerResolution: Signal.of(this.resolution) - } - - const $style = style(feature) - $style.on(feature.setStyle.bind(feature)) - once(() => this.emit('addfeatures', { features: [feature] }), $style) - - - // Use dedicated function to update feature coordinates from within - // modify interaction. Such internal changes must not trigger ModifyEvent. - - feature.internalChange = Signal.of(false) - - feature.updateCoordinates = coordinates => { - feature.internalChange(true) - setCoordinates(feature.getGeometry(), coordinates) - feature.internalChange(false) - } - - feature.commit = () => { - // Event must be deferred so that event handler has a chance - // to update to a new state (drag -> selected). - setTimeout(() => feature.dispatchEvent({ type: 'change', target: feature })) - } - - return feature } From 8b067c2edfbc3c627c219f8a63af0428e896ec67 Mon Sep 17 00:00:00 2001 From: dehmer Date: Fri, 5 Jul 2024 06:13:25 +0200 Subject: [PATCH 32/73] polygon labels. --- paperwork/polygon-styles.dot | 80 ++++++++++++++++++ paperwork/polygon-styles.pdf | Bin 0 -> 33217 bytes src/renderer/ol/style/labels.js | 13 ++- .../ol/style/polygon-styles/placement.js | 13 ++- src/renderer/ol/style/polygon.js | 38 +++++++-- 5 files changed, 130 insertions(+), 14 deletions(-) create mode 100644 paperwork/polygon-styles.dot create mode 100644 paperwork/polygon-styles.pdf diff --git a/paperwork/polygon-styles.dot b/paperwork/polygon-styles.dot new file mode 100644 index 00000000..9d73220c --- /dev/null +++ b/paperwork/polygon-styles.dot @@ -0,0 +1,80 @@ +digraph polygon { + node [shape=record]; + + // inputs + globalStyle [label="globalStyle\n\{k: v\}", color=blue] + layerStyle [label="layerStyle\n\{k: v\}", color=blue] + featureStyle [label="featureStyle\n\{k: v\}", color=blue] + feature [label="feature\nol/Feature", color=blue] + resolution [label="resolution\nNumber", color=blue] + properties [label="properties\n\{k: v\}"]; + geometry [label="geometry\nol/geom/Geometry"] + sidc [label="sidc\nString"] + colorScheme [label="colorScheme\nString"] + schemeStyle [label="schemeStyle\n\{k: v\}"] + effectiveStyle [label="effectiveStyle\n\{k: v\}"] + read [label="read\nol/Geometry =\> jts/Geomery"] + write [label="write\njts/Geometry =\> ol/Geomery"] + _pointResolution [label="_pointResolution\nNumber =\> Number"] + pointResolution [label="pointResolution\nNumber"] + // utmGeometry [label="utmGeometry\njts/Geometry"] + parameterizedSIDC [label="parameterizedSIDC\nString"] + labels [label="labels\n[\{k: v\}]"] + evalSync [label="evalSync\n\{k: v\} =\> \{k: v\}"] + simplifiedGeometry [label="simplifiedGeometry\nol/Geometry"] + lineSmoothing [label="lineSmoothing\nBoolean"] + smoothenedGeometry [label="smoothenedGeometry\nBoolean"] + utmSmoothenedGeometry [label="utmSmoothenedGeometry\njts/Geometry"] + context [label="context\n\{k: v\}"] + shape [label="shape\n\{k: v\}"] + placement [label="placement\n\{k: v\} =\> \{k: v\}"] + styles [label="styles\n[\{k: v\}]"] + style [label="style\nol/Style", color=red] + + // shared + feature -> properties + properties -> geometry + properties -> sidc + globalStyle -> colorScheme + layerStyle -> colorScheme + featureStyle -> colorScheme + sidc -> schemeStyle + colorScheme -> schemeStyle + globalStyle -> effectiveStyle + schemeStyle -> effectiveStyle + layerStyle -> effectiveStyle + featureStyle -> effectiveStyle + effectiveStyle -> styleRegistry + + // shape + geometry -> read + geometry -> write + geometry -> _pointResolution + _pointResolution -> pointResolution + resolution -> pointResolution + read -> utmGeometry + geometry -> utmGeometry + sidc -> parameterizedSIDC + properties -> evalSync + sidc -> evalSync + geometry -> simplifiedGeometry + resolution -> simplifiedGeometry + effectiveStyle -> lineSmoothing + lineSmoothing -> smoothenedGeometry + simplifiedGeometry -> smoothenedGeometry + smoothenedGeometry -> utmSmoothenedGeometry + read -> utmSmoothenedGeometry + utmSmoothenedGeometry -> placement + utmSmoothenedGeometry -> context + parameterizedSIDC -> labels + evalSync -> labels + placement -> labels + pointResolution -> context + context -> shape + parameterizedSIDC -> shape + labels -> styles + shape -> styles + styles -> style + styleRegistry -> style + write -> style +} \ No newline at end of file diff --git a/paperwork/polygon-styles.pdf b/paperwork/polygon-styles.pdf new file mode 100644 index 0000000000000000000000000000000000000000..fae59ee8037bbfa3e9462fc866e17a93e561603f GIT binary patch literal 33217 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 literal 0 HcmV?d00001 diff --git a/src/renderer/ol/style/labels.js b/src/renderer/ol/style/labels.js index f14db774..f89c6ca5 100644 --- a/src/renderer/ol/style/labels.js +++ b/src/renderer/ol/style/labels.js @@ -101,9 +101,9 @@ export const evalSync = context => { ? textField.map(evalSync).filter(Boolean).join('\n') : jexl.evalSync(textField, context) - return props => { - props = Array.isArray(props) ? props : [props] - return props.reduce((acc, spec) => { + const replaceOne = properties => { + properties = Array.isArray(properties) ? properties : [properties] + return properties.reduce((acc, spec) => { if (!spec['text-field']) acc.push(spec) else { const textField = evalSync(spec['text-field']) @@ -113,4 +113,11 @@ export const evalSync = context => { return acc }, []) } + + const replaceAll = arg => { + if (!Array.isArray(arg)) return replaceAll(arg) + return arg.flatMap(replaceOne) + } + + return replaceAll } diff --git a/src/renderer/ol/style/polygon-styles/placement.js b/src/renderer/ol/style/polygon-styles/placement.js index e27d8fc3..de1c6b94 100644 --- a/src/renderer/ol/style/polygon-styles/placement.js +++ b/src/renderer/ol/style/polygon-styles/placement.js @@ -1,3 +1,4 @@ +import * as R from 'ramda' import * as TS from '../../ts' const lazy = function (fn) { @@ -49,14 +50,22 @@ const placement = geometry => { right: lazy(() => TS.point(xIntersection()[1])) } - return properties => { + const tryer = properties => { const anchor = properties['text-anchor'] const geometry = Number.isFinite(anchor) ? fraction(anchor) : anchors[anchor || 'center']() - return { geometry, ...properties } } + + const catcher = (err, properties) => console.warn(err, properties) + + const calculate = arg => { + if (!Array.isArray(arg)) return calculate([arg]) + else return arg.map(R.tryCatch(tryer, catcher)).filter(Boolean) + } + + return calculate } export default placement diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js index 884a6997..b3b9ec92 100644 --- a/src/renderer/ol/style/polygon.js +++ b/src/renderer/ol/style/polygon.js @@ -1,13 +1,21 @@ +import * as R from 'ramda' import Signal from '@syncpoint/signal' +import * as TS from '../ts' +import * as Math from '../../../shared/Math' import transform from './_transform' import { parameterized } from '../../symbology/2525c' -import polygonLabels from './polygon-styles/labels' +import labels from './polygon-styles/labels' +import styles from './polygon-styles/index' import placement from './polygon-styles/placement' import evalSync from './_evalSync' import smoothenedGeometry from './_smoothenedGeometry' -import defaultStyle from './defaultStyle' +import { styleFactory } from './styleFactory' -const labels = sidc => (polygonLabels[sidc] || []).flat() +const { link } = Signal + +const _context = (geometry, resolution) => ({ TS, ...Math, geometry, resolution}) +const _labels = sidc => (labels[sidc] || []).flat() +const _shape = sidc => styles[sidc] || styles.DEFAULT const lineSmoothing = style => style['line-smooth'] || false const simplifiedGeometry = (geometry, resolution) => { const coordinates = geometry.getCoordinates() @@ -22,15 +30,27 @@ export default $ => { $.read = read $.write = write $.pointResolution = $.resolution.ap(pointResolution) - $.utmGeometry = $.geometry.ap($.read) + // $.utmGeometry = $.geometry.ap($.read) $.parameterizedSIDC = $.sidc.map(parameterized) - $.labels = $.parameterizedSIDC.map(labels) - $.evalSync = Signal.link(evalSync, [$.sidc, $.properties]) - $.simplifiedGeometry = Signal.link(simplifiedGeometry, [$.geometry, $.resolution]) + $.evalSync = link(evalSync, [$.sidc, $.properties]) + $.simplifiedGeometry = link(simplifiedGeometry, [$.geometry, $.resolution]) $.lineSmoothing = $.effectiveStyle.map(lineSmoothing) - $.smoothenedGeometry = Signal.link(smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) + $.smoothenedGeometry = link(smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) $.utmSmoothenedGeometry = $.smoothenedGeometry.ap($.read) + $.context = link(_context, [$.utmSmoothenedGeometry, $.pointResolution]) + $.shape = $.context.ap($.parameterizedSIDC.map(_shape)) $.placement = $.utmSmoothenedGeometry.map(placement) + $.labels = $.parameterizedSIDC + .map(_labels) + .ap($.evalSync) + .ap($.placement) + + $.styles = link((...styles) => styles.reduce(R.concat), [$.labels, $.shape]) - return $.smoothenedGeometry.map(geometry => defaultStyle({ geometry })) + return link((styles, styleRegistry, write) => { + return styles + .map(styleRegistry) + .map(({ geometry, ...rest }) => ({ geometry: write(geometry), ...rest })) + .flatMap(styleFactory) + }, [$.styles, $.styleRegistry, $.write, $.evalSync]) } From ef5d50d43a092be4537111ab9abb0eba00fee094 Mon Sep 17 00:00:00 2001 From: dehmer Date: Fri, 5 Jul 2024 07:11:51 +0200 Subject: [PATCH 33/73] modify: fixed issue with sticky pointer. --- src/renderer/ol/interaction/modify/events.js | 8 +++++++- src/renderer/ol/interaction/modify/index.js | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/renderer/ol/interaction/modify/events.js b/src/renderer/ol/interaction/modify/events.js index e1782fbc..34b60958 100644 --- a/src/renderer/ol/interaction/modify/events.js +++ b/src/renderer/ol/interaction/modify/events.js @@ -119,7 +119,13 @@ export const pointer = (options, rbush, event) => { pointer.pick = () => { const [segment] = sortedSegments() const [coordinate, index] = vertex(segment) - return { segment, coordinate, index } + + // Coordinate must never be undefined in order + // to be handled correctly as a signal. + return coordinate === undefined + ? ({ segment, coordinate: null, index }) + : ({ segment, coordinate, index }) + } return pointer diff --git a/src/renderer/ol/interaction/modify/index.js b/src/renderer/ol/interaction/modify/index.js index 1cb2d6e7..39e840a3 100644 --- a/src/renderer/ol/interaction/modify/index.js +++ b/src/renderer/ol/interaction/modify/index.js @@ -127,7 +127,7 @@ export class Modify extends Interaction { // Filter coordinates from state loop. const coordinate = R.compose( R.map(({ coordinate }) => coordinate), - R.filter(event => event.type === 'coordinate'), + R.filter(event => event.type === 'coordinate') )(stateLoop) // modifyEvent :: Signal From 393ef09b25ebc2c9b52fd39a86f60a8604d08c75 Mon Sep 17 00:00:00 2001 From: dehmer Date: Sun, 7 Jul 2024 12:23:06 +0200 Subject: [PATCH 34/73] geometry now updating prpoerly. --- package-lock.json | 2 +- src/renderer/model/sources/featureSource.js | 7 ++++++- src/renderer/ol/style/styles.js | 9 ++++++++- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ce3e406e..c5b41cca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2937,7 +2937,7 @@ }, "node_modules/@syncpoint/signal": { "version": "1.2.0", - "resolved": "git+ssh://git@github.com/syncpoint/signal.git#6d66285d8b99fe28201352a3ec044158fd6fdf41" + "resolved": "git+ssh://git@github.com/syncpoint/signal.git#e9fc0d69a429e989fd8bc06588a1d58ae1e0e62c" }, "node_modules/@syncpoint/signs": { "version": "1.1.0", diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index 27c172d3..dd9edb13 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -22,7 +22,7 @@ const readFeature = R.curry((state, source) => { const layerId = ID.layerId(featureId) feature.$ = { - feature: Signal.of(feature), + feature: Signal.of(feature, { equals: R.F }), globalStyle: Signal.of(state.styles[ID.defaultStyleId]), layerStyle: Signal.of(state.styles[ID.styleId(layerId)] ?? {}), featureStyle: Signal.of(state.styles[ID.styleId(featureId)] ?? {}), @@ -49,6 +49,11 @@ const readFeature = R.curry((state, source) => { setTimeout(() => feature.dispatchEvent({ type: 'change', target: feature })) } + feature.on('change', ({ target }) => { + console.log(target) + target.$.feature(target) + }) + return feature }) diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index fc5591e8..24194b0d 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -5,6 +5,7 @@ import styleRegistry from './styleRegistry' import point from './point' import polygon from './polygon' import defaultStyle from './defaultStyle' +import isEqual from 'react-fast-compare' import colorScheme from './_colorScheme' import schemeStyle from './_schemeStyle' @@ -12,9 +13,15 @@ import effectiveStyle from './_effectiveStyle' export default feature => { const { $ } = feature + const createkey = target => `${target.ol_uid}:${target.getRevision()}` + const distinct = (key, target) => key === createkey(target) + ? [key, undefined] + : [createkey(target), target] $.properties = $.feature.map(feature => feature.getProperties()) - $.geometry = $.properties.map(({ geometry }) => geometry) + $.properties.equals = isEqual + $.geometry = Signal.loop((key, feature) => distinct(key, feature.getGeometry()), null, $.feature) + $.geometry.equals = R.F $.sidc = $.properties.map(R.prop('sidc')) $.colorScheme = Signal.link(colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) $.schemeStyle = Signal.link(schemeStyle, [$.sidc, $.colorScheme]) From 31dcecfceeef8849de476ee562b7c7bbbb7a1f7f Mon Sep 17 00:00:00 2001 From: dehmer Date: Sun, 7 Jul 2024 17:59:06 +0200 Subject: [PATCH 35/73] simplified feature source(s). --- src/renderer/model/sources/__featureSource.js | 24 ---- src/renderer/model/sources/featureSource.js | 126 +++++++----------- src/renderer/ol/style/keyequals.js | 13 ++ src/renderer/ol/style/point.js | 6 +- src/renderer/ol/style/polygon.js | 1 - src/renderer/ol/style/styles.js | 11 +- src/shared/signal.js | 16 +++ 7 files changed, 88 insertions(+), 109 deletions(-) delete mode 100644 src/renderer/model/sources/__featureSource.js create mode 100644 src/renderer/ol/style/keyequals.js diff --git a/src/renderer/model/sources/__featureSource.js b/src/renderer/model/sources/__featureSource.js deleted file mode 100644 index 5b84e928..00000000 --- a/src/renderer/model/sources/__featureSource.js +++ /dev/null @@ -1,24 +0,0 @@ -import VectorSource from 'ol/source/Vector' -import * as ID from '../../ids' - -/** - * @deprecated - */ -export const featureSource = (featureStore, scope) => { - const matchesScope = feature => feature - ? ID.isId(scope)(feature.getId()) - : false - - const source = new VectorSource({ features: [] }) - - featureStore.on('addfeatures', ({ features }) => { - source.addFeatures(features.filter(matchesScope)) - }) - - featureStore.on('removefeatures', ({ features }) => features - .filter(matchesScope) - .forEach(feature => source.removeFeature(feature)) - ) - - return source -} diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index dd9edb13..148bb554 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -6,6 +6,7 @@ import * as ID from '../../ids' import styles from '../../ol/style/styles' import { flatten, select } from '../../../shared/signal' import { setCoordinates } from '../geometry' +import keyequals from '../../ol/style/keyequals' const format = new GeoJSON({ dataProjection: 'EPSG:3857', @@ -22,11 +23,19 @@ const readFeature = R.curry((state, source) => { const layerId = ID.layerId(featureId) feature.$ = { - feature: Signal.of(feature, { equals: R.F }), + + // A word of caution: It is strongly adviced to NOT use feature signal + // DIRECTLY to derive style! Setting the featues style will update the + // feature's revision and thus lead to a infinate loop. + // Always make sure to extract relevant information from feature into + // new signals which conversely are only updated when this information + // has actually changed. + // + feature: Signal.of(feature, { equals: keyequals() }), globalStyle: Signal.of(state.styles[ID.defaultStyleId]), layerStyle: Signal.of(state.styles[ID.styleId(layerId)] ?? {}), featureStyle: Signal.of(state.styles[ID.styleId(featureId)] ?? {}), - centerResolution: Signal.of(state.resolution) + resolution: Signal.of(state.resolution) } feature.$.styles = styles(feature) @@ -50,7 +59,6 @@ const readFeature = R.curry((state, source) => { } feature.on('change', ({ target }) => { - console.log(target) target.$.feature(target) }) @@ -73,103 +81,71 @@ const ord = R.cond([ const isCandidateId = id => ID.isFeatureId(id) || ID.isMarkerId(id) || ID.isMeasureId(id) +const operations = R.compose( + flatten, + R.map(R.sort((a, b) => ord(a) - ord(b))), + R.map(R.prop('operations')) +) + +const selectEvent = select([ + R.propEq(ID.defaultStyleId, 'key'), + R.compose(ID.isLayerStyleId, R.prop('key')), + R.compose(ID.isFeatureStyleId, R.prop('key')), + R.compose(isCandidateId, R.prop('key')) +]) + /** * */ export const featureSource = services => { const { store } = services - const state = { loaded: false } + const state = {} + + const source = new VectorSource({ + features: [], + useSpatialIndex: false, + strategy: (extent, resolution) => { + state.resolution = resolution + return [extent] + } + }) + + ;(async () => { + state.styles = await store.dictionary('style+') + const features = (await store.tuples(ID.FEATURE_SCOPE)) + .map(([id, value]) => ({ id, ...value })) + .map(readFeature(state)) + source.addFeatures(features) + })() // ==> batch event handling - const operations = R.compose( - flatten, - R.map(R.sort((a, b) => ord(a) - ord(b))), - R.map(R.prop('operations')) - )(Signal.fromListeners(['batch'], store)) - - const [ - globalStyle, - layerStyle, - featureStyle, - feature - ] = select([ - R.propEq(ID.defaultStyleId, 'key'), - R.compose(ID.isLayerStyleId, R.prop('key')), - R.compose(ID.isFeatureStyleId, R.prop('key')), - R.compose(isCandidateId, R.prop('key')) - ], operations) + const events = operations(Signal.fromListeners(['batch'], store)) + const [globalStyle, layerStyle, featureStyle, feature] = selectEvent(events) globalStyle.on(({ value }) => { - indexedSource.getFeatures().forEach(feature => feature.$.globalStyle(value)) + console.log(value) + source.getFeatures().forEach(feature => feature.$.globalStyle(value)) }) layerStyle.on(({ type, key, value }) => { const layerId = ID.layerId(key) - indexedSource.getFeatures() + source.getFeatures() .filter(feature => ID.layerId(feature.getId()) === layerId) .forEach(feature => feature.$.layerStyle(type === 'put' ? value : {})) }) featureStyle.on(({ type, key, value }) => { const featureId = ID.featureId(key) - const feature = indexedSource.getFeatureById(featureId) + const feature = source.getFeatureById(featureId) if (feature) feature.$.featureStyle(type === 'put' ? value : {}) }) - // <== batch event handling - - // Source with spatial index holding all features. - const indexedSource = new VectorSource({ - useSpatialIndex: true, - features: [] + feature.on(({ type, key, value }) => { + // TODO: ... }) - // Fetch all features and styles on initial load. - const populate = async () => { - state.loaded = true - state.styles = await store.dictionary('style+') - const features = (await store.tuples(ID.FEATURE_SCOPE)) - .map(([id, value]) => ({ id, ...value })) - .map(readFeature(state)) - indexedSource.addFeatures(features) - } - - const loader = async extent => { - if (!state.loaded) await populate() - - // Diff features currently in view and features from extent. - const loaded = source.getFeatures() - const loading = indexedSource.getFeaturesInExtent(extent) - const difference = R.differenceWith((a, b) => a.getId() === b.getId()) - source.removeFeatures(difference(loaded, loading)) - source.addFeatures(difference(loading, loaded)) - - // Apply resolution to all features currently in view: - source.getFeatures() - .filter(feature => feature.$.resolution) - .forEach(feature => feature.$.resolution(state.resolution)) - } - - // Workaround to always trigger loader for updated extent. - // See also: https://gis.stackexchange.com/questions/322681/openlayers-refresh-vector-source-with-bbox-strategy-after-map-moved - - const strategy = (extent, resolution) => { - const bbox = extent.join(',') - if (bbox === state.bbox) return [] - state.bbox = bbox - state.previousResolution = state.resolution - state.resolution = resolution - loader(extent) - return [] - } - - const source = new VectorSource({ - features: [], - useSpatialIndex: false, - loader: () => {}, - strategy - }) + // <== batch event handling return source } diff --git a/src/renderer/ol/style/keyequals.js b/src/renderer/ol/style/keyequals.js new file mode 100644 index 00000000..ecb11407 --- /dev/null +++ b/src/renderer/ol/style/keyequals.js @@ -0,0 +1,13 @@ +/** + * Stateful equality function to compare old/new keys of OL features/geometries. + */ +const keyequals = () => { + const key = target => `${target.ol_uid}:${target.getRevision()}` + let last + return (_, b) => { + if (last === key(b)) return true + else last = key(b); return false + } +} + +export default keyequals diff --git a/src/renderer/ol/style/point.js b/src/renderer/ol/style/point.js index 81e3fd13..5214a9bf 100644 --- a/src/renderer/ol/style/point.js +++ b/src/renderer/ol/style/point.js @@ -6,8 +6,8 @@ import { styleFactory } from './styleFactory' /** * */ -export default $ => Signal.link((feature, styleRegistry) => { - const { geometry, sidc, ...properties } = feature.getProperties() +export default $ => Signal.link((properties, geometry, styleRegistry) => { + const sidc = properties.sidc const modifiers = Object.entries(properties) .filter(([key, value]) => MODIFIERS[key] && value) @@ -21,4 +21,4 @@ export default $ => Signal.link((feature, styleRegistry) => { }] .map(styleRegistry) .flatMap(styleFactory) -}, [$.feature, $.styleRegistry]) +}, [$.properties, $.geometry, $.styleRegistry]) \ No newline at end of file diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js index b3b9ec92..671a298c 100644 --- a/src/renderer/ol/style/polygon.js +++ b/src/renderer/ol/style/polygon.js @@ -25,7 +25,6 @@ const simplifiedGeometry = (geometry, resolution) => { } export default $ => { - $.resolution = Signal.of() const [read, write, pointResolution] = transform($.geometry) $.read = read $.write = write diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index 24194b0d..3f5ed36b 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -6,6 +6,7 @@ import point from './point' import polygon from './polygon' import defaultStyle from './defaultStyle' import isEqual from 'react-fast-compare' +import keyequals from './keyequals' import colorScheme from './_colorScheme' import schemeStyle from './_schemeStyle' @@ -13,15 +14,13 @@ import effectiveStyle from './_effectiveStyle' export default feature => { const { $ } = feature - const createkey = target => `${target.ol_uid}:${target.getRevision()}` - const distinct = (key, target) => key === createkey(target) - ? [key, undefined] - : [createkey(target), target] $.properties = $.feature.map(feature => feature.getProperties()) $.properties.equals = isEqual - $.geometry = Signal.loop((key, feature) => distinct(key, feature.getGeometry()), null, $.feature) - $.geometry.equals = R.F + + $.geometry = $.feature.map(feature => feature.getGeometry()) + $.geometry.equals = keyequals() // currently no other way to set equals for map + $.sidc = $.properties.map(R.prop('sidc')) $.colorScheme = Signal.link(colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) $.schemeStyle = Signal.link(schemeStyle, [$.sidc, $.colorScheme]) diff --git a/src/shared/signal.js b/src/shared/signal.js index e914309f..94421ce0 100644 --- a/src/shared/signal.js +++ b/src/shared/signal.js @@ -56,3 +56,19 @@ export const once = (fn, signal) => { fn(value) }) } + +export const circuitBreaker = (input) => { + let last = 0 + let count = 0 + + return input.map(x => { + if ((Date.now() - last) < 10) count++ + last = Date.now() + + if (count > 10) { + console.warn('frequency too high', x) + return undefined + } + else return x + }) +} From 5e427b6d1ce0526a0396c7585f1d1df75b6b57b3 Mon Sep 17 00:00:00 2001 From: dehmer Date: Sun, 7 Jul 2024 19:53:47 +0200 Subject: [PATCH 36/73] forward updated resolution. --- src/renderer/model/sources/featureSource.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index 148bb554..d38b7e32 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -104,8 +104,11 @@ export const featureSource = services => { const source = new VectorSource({ features: [], useSpatialIndex: false, - strategy: (extent, resolution) => { - state.resolution = resolution + strategy: function (extent, resolution) { + if (state.resolution !== resolution) { + state.resolution = resolution + this.getFeatures().map(feature => feature.$.resolution(resolution)) + } return [extent] } }) @@ -124,7 +127,6 @@ export const featureSource = services => { const [globalStyle, layerStyle, featureStyle, feature] = selectEvent(events) globalStyle.on(({ value }) => { - console.log(value) source.getFeatures().forEach(feature => feature.$.globalStyle(value)) }) From 22e092a884de19d98b11f89125fbc49164c42ed8 Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 8 Jul 2024 09:22:49 +0200 Subject: [PATCH 37/73] selection styles. --- src/renderer/model/sources/featureSource.js | 17 +++++- src/renderer/ol/style/_context.js | 4 ++ src/renderer/ol/style/_labels.js | 14 ++--- src/renderer/ol/style/_lineSmoothing.js | 5 ++ src/renderer/ol/style/_placement.js | 4 +- src/renderer/ol/style/_selection.js | 22 +++++++ src/renderer/ol/style/_shape.js | 5 ++ .../ol/style/linestring-styles/placement.js | 11 +++- src/renderer/ol/style/linestring.js | 58 +++++++++++++++++++ src/renderer/ol/style/point.js | 47 ++++++++++----- src/renderer/ol/style/polygon.js | 54 ++++++++--------- src/renderer/ol/style/styles.js | 10 +++- 12 files changed, 192 insertions(+), 59 deletions(-) create mode 100644 src/renderer/ol/style/_context.js create mode 100644 src/renderer/ol/style/_lineSmoothing.js create mode 100644 src/renderer/ol/style/_selection.js create mode 100644 src/renderer/ol/style/_shape.js create mode 100644 src/renderer/ol/style/linestring.js diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index d38b7e32..a480a579 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -35,7 +35,8 @@ const readFeature = R.curry((state, source) => { globalStyle: Signal.of(state.styles[ID.defaultStyleId]), layerStyle: Signal.of(state.styles[ID.styleId(layerId)] ?? {}), featureStyle: Signal.of(state.styles[ID.styleId(featureId)] ?? {}), - resolution: Signal.of(state.resolution) + centerResolution: Signal.of(state.resolution), + selectionMode: Signal.of('default') } feature.$.styles = styles(feature) @@ -98,7 +99,7 @@ const selectEvent = select([ * */ export const featureSource = services => { - const { store } = services + const { store, selection } = services const state = {} const source = new VectorSource({ @@ -107,7 +108,7 @@ export const featureSource = services => { strategy: function (extent, resolution) { if (state.resolution !== resolution) { state.resolution = resolution - this.getFeatures().map(feature => feature.$.resolution(resolution)) + this.getFeatures().map(feature => feature.$.centerResolution(resolution)) } return [extent] } @@ -149,5 +150,15 @@ export const featureSource = services => { // <== batch event handling + selection.on('selection', ({ deselected }) => { + const mode = selection.selected().length > 1 + ? 'multiselect' + : 'singleselect' + + const apply = mode => feature => feature.$.selectionMode(mode) + deselected.map(source.getFeatureById.bind(source)).map(apply('default')) + selection.selected().map(source.getFeatureById.bind(source)).map(apply(mode)) + }) + return source } diff --git a/src/renderer/ol/style/_context.js b/src/renderer/ol/style/_context.js new file mode 100644 index 00000000..fcf363f5 --- /dev/null +++ b/src/renderer/ol/style/_context.js @@ -0,0 +1,4 @@ +import * as TS from '../ts' +import * as Math from '../../../shared/Math' + +export default (geometry, resolution) => ({ TS, ...Math, geometry, resolution }) diff --git a/src/renderer/ol/style/_labels.js b/src/renderer/ol/style/_labels.js index 2a409737..e03f8724 100644 --- a/src/renderer/ol/style/_labels.js +++ b/src/renderer/ol/style/_labels.js @@ -1,11 +1,5 @@ -import polygonLabels from './polygon-styles/labels' -import lineStringLabels from './linestring-styles/labels' -import multiPointLabels from './multipoint-styles/labels' -const LABELS = { - Polygon: polygonLabels, - LineString: lineStringLabels, - MultiPoint: multiPointLabels -} - -export default (geometryType, sidc) => ((LABELS[geometryType] || {})[sidc] || []).flat() +/** + * + */ +export default labels => sidc => (labels[sidc] || []).flat() diff --git a/src/renderer/ol/style/_lineSmoothing.js b/src/renderer/ol/style/_lineSmoothing.js new file mode 100644 index 00000000..d5163a5c --- /dev/null +++ b/src/renderer/ol/style/_lineSmoothing.js @@ -0,0 +1,5 @@ + +/** + * + */ +export default style => style['line-smooth'] || false diff --git a/src/renderer/ol/style/_placement.js b/src/renderer/ol/style/_placement.js index 7456a557..5120007d 100644 --- a/src/renderer/ol/style/_placement.js +++ b/src/renderer/ol/style/_placement.js @@ -20,8 +20,8 @@ export default $ => { const placement = $.geometryType.map(geometryType => PLACEMENT[geometryType] || R.identity) const geometry = $.geometryType.chain(geometryType => { return R.cond([ - [R.equals('LineString'), R.always($.utmSmoothenedGeometry)], - [R.equals('Polygon'), R.always($.utmSmoothenedGeometry)], + [R.equals('LineString'), R.always($.jtsSmoothenedGeometry)], + [R.equals('Polygon'), R.always($.jtsSmoothenedGeometry)], [R.equals('MultiPoint'), R.always($.geometry.ap($.read).map(pointBuffer))] ])(geometryType) }) diff --git a/src/renderer/ol/style/_selection.js b/src/renderer/ol/style/_selection.js new file mode 100644 index 00000000..568d1ddc --- /dev/null +++ b/src/renderer/ol/style/_selection.js @@ -0,0 +1,22 @@ +import * as R from 'ramda' +import * as TS from '../ts' + +export default (mode, geometry) => { + const selection = [] + const guideline = mode === 'singleselect' + ? { id: 'style:guide-stroke', geometry } + : null + + const points = () => TS.points(geometry) + + const handles = R.cond([ + [R.equals('default'), R.always(null)], + [R.equals('singleselect'), R.always({ id: 'style:circle-handle', geometry: TS.multiPoint(points()) })], + [R.equals('multiselect'), R.always({ id: 'style:rectangle-handle', geometry: points()[0] })] + ])(mode) + + guideline && selection.push(guideline) + handles && selection.push(handles) + + return selection +} diff --git a/src/renderer/ol/style/_shape.js b/src/renderer/ol/style/_shape.js new file mode 100644 index 00000000..10131abf --- /dev/null +++ b/src/renderer/ol/style/_shape.js @@ -0,0 +1,5 @@ + +/** + * + */ +export default styles => sidc => styles[sidc] || styles.DEFAULT diff --git a/src/renderer/ol/style/linestring-styles/placement.js b/src/renderer/ol/style/linestring-styles/placement.js index 22e2d92a..090b06d8 100644 --- a/src/renderer/ol/style/linestring-styles/placement.js +++ b/src/renderer/ol/style/linestring-styles/placement.js @@ -37,7 +37,7 @@ const placement = geometry => { const normalize = angle => TS.Angle.normalize(TS.Angle.PI_TIMES_2 - angle) - return properties => { + const tryer = properties => { const rotate = properties['text-field'] ? 'text-rotate' : 'icon-rotate' const anchor = properties['text-anchor'] || properties['icon-anchor'] || @@ -50,6 +50,15 @@ const placement = geometry => { [rotate]: normalize(angle(anchor)) } } + + const catcher = (err, properties) => console.warn(err, properties) + + const calculate = arg => { + if (!Array.isArray(arg)) return calculate([arg]) + else return arg.map(R.tryCatch(tryer, catcher)).filter(Boolean) + } + + return calculate } export default placement diff --git a/src/renderer/ol/style/linestring.js b/src/renderer/ol/style/linestring.js new file mode 100644 index 00000000..6c9c8149 --- /dev/null +++ b/src/renderer/ol/style/linestring.js @@ -0,0 +1,58 @@ +import * as R from 'ramda' +import Signal from '@syncpoint/signal' +import { parameterized } from '../../symbology/2525c' +import labels from './linestring-styles/labels' +import styles from './linestring-styles/index' +import placement from './linestring-styles/placement' +import { styleFactory } from './styleFactory' + +import _evalSync from './_evalSync' +import _smoothenedGeometry from './_smoothenedGeometry' +import _context from './_context' +import _labels from './_labels' +import _shape from './_shape' +import _lineSmoothing from './_lineSmoothing' +import _selection from './_selection' + +const _simplifiedGeometry = (geometry, resolution) => { + const coordinates = geometry.getCoordinates() + return coordinates.length > 50 + ? geometry.simplify(resolution) + : geometry +} + +export default $ => { + const { link } = Signal + + $.resolution = $.centerResolution.ap($.pointResolution) + $.parameterizedSIDC = $.sidc.map(parameterized) + $.evalSync = link(_evalSync, [$.sidc, $.properties]) + $.simplifiedGeometry = link(_simplifiedGeometry, [$.geometry, $.centerResolution]) + $.jtsSimplifiedGeometry = $.simplifiedGeometry.ap($.read) + $.lineSmoothing = $.effectiveStyle.map(_lineSmoothing) + $.smoothenedGeometry = link(_smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) + $.jtsSmoothenedGeometry = $.smoothenedGeometry.ap($.read) + $.context = link(_context, [$.jtsSmoothenedGeometry, $.resolution]) + $.placement = $.jtsSmoothenedGeometry.map(placement) + + // ==> Mandatory slots to fill to derive resulting style: + + $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) + $.selection = Signal.link(_selection, [$.selectionMode, $.jtsSimplifiedGeometry]) + $.labels = $.parameterizedSIDC + .map(_labels(labels)) + .ap($.evalSync) + .ap($.placement) + + // <== Mandatory slots + + $.styleFactory = Signal.of(styleFactory) + $.styles = link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) + + return link((styles, styleRegistry, write) => { + return styles + .map(styleRegistry) + .map(({ geometry, ...rest }) => ({ geometry: write(geometry), ...rest })) + .flatMap(styleFactory) + }, [$.styles, $.styleRegistry, $.write, $.evalSync]) +} diff --git a/src/renderer/ol/style/point.js b/src/renderer/ol/style/point.js index 5214a9bf..d34e6a3c 100644 --- a/src/renderer/ol/style/point.js +++ b/src/renderer/ol/style/point.js @@ -6,19 +6,34 @@ import { styleFactory } from './styleFactory' /** * */ -export default $ => Signal.link((properties, geometry, styleRegistry) => { - const sidc = properties.sidc - - const modifiers = Object.entries(properties) - .filter(([key, value]) => MODIFIERS[key] && value) - .reduce((acc, [key, value]) => R.tap(acc => (acc[MODIFIERS[key]] = value), acc), {}) - - return [{ - id: 'style:2525c/symbol', - geometry, - 'symbol-code': sidc, - 'symbol-modifiers': modifiers - }] - .map(styleRegistry) - .flatMap(styleFactory) -}, [$.properties, $.geometry, $.styleRegistry]) \ No newline at end of file +export default $ => { + + // Point also need to contribute simplified JTS/UTM geometry, + // because it is used to derive selection styles. + // + $.jtsSimplifiedGeometry = $.geometry.ap($.read) + + // ==> Mandatory slots to fill to derive resulting style: + + $.labels = Signal.of([]) + $.selection = Signal.of([]) + + // <== Mandatory slots + + return Signal.link((properties, geometry, styleRegistry) => { + const sidc = properties.sidc + + const modifiers = Object.entries(properties) + .filter(([key, value]) => MODIFIERS[key] && value) + .reduce((acc, [key, value]) => R.tap(acc => (acc[MODIFIERS[key]] = value), acc), {}) + + return [{ + id: 'style:2525c/symbol', + geometry, + 'symbol-code': sidc, + 'symbol-modifiers': modifiers + }] + .map(styleRegistry) + .flatMap(styleFactory) + }, [$.properties, $.geometry, $.styleRegistry]) +} diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js index 671a298c..80f0c4d7 100644 --- a/src/renderer/ol/style/polygon.js +++ b/src/renderer/ol/style/polygon.js @@ -1,23 +1,20 @@ import * as R from 'ramda' import Signal from '@syncpoint/signal' -import * as TS from '../ts' -import * as Math from '../../../shared/Math' -import transform from './_transform' import { parameterized } from '../../symbology/2525c' import labels from './polygon-styles/labels' import styles from './polygon-styles/index' import placement from './polygon-styles/placement' -import evalSync from './_evalSync' -import smoothenedGeometry from './_smoothenedGeometry' import { styleFactory } from './styleFactory' -const { link } = Signal +import _evalSync from './_evalSync' +import _smoothenedGeometry from './_smoothenedGeometry' +import _context from './_context' +import _labels from './_labels' +import _shape from './_shape' +import _lineSmoothing from './_lineSmoothing' +import _selection from './_selection' -const _context = (geometry, resolution) => ({ TS, ...Math, geometry, resolution}) -const _labels = sidc => (labels[sidc] || []).flat() -const _shape = sidc => styles[sidc] || styles.DEFAULT -const lineSmoothing = style => style['line-smooth'] || false -const simplifiedGeometry = (geometry, resolution) => { +const _simplifiedGeometry = (geometry, resolution) => { const coordinates = geometry.getCoordinates() return coordinates[0].length > 50 ? geometry.simplify(resolution) @@ -25,26 +22,31 @@ const simplifiedGeometry = (geometry, resolution) => { } export default $ => { - const [read, write, pointResolution] = transform($.geometry) - $.read = read - $.write = write - $.pointResolution = $.resolution.ap(pointResolution) - // $.utmGeometry = $.geometry.ap($.read) + const { link } = Signal + + $.resolution = $.centerResolution.ap($.pointResolution) $.parameterizedSIDC = $.sidc.map(parameterized) - $.evalSync = link(evalSync, [$.sidc, $.properties]) - $.simplifiedGeometry = link(simplifiedGeometry, [$.geometry, $.resolution]) - $.lineSmoothing = $.effectiveStyle.map(lineSmoothing) - $.smoothenedGeometry = link(smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) - $.utmSmoothenedGeometry = $.smoothenedGeometry.ap($.read) - $.context = link(_context, [$.utmSmoothenedGeometry, $.pointResolution]) - $.shape = $.context.ap($.parameterizedSIDC.map(_shape)) - $.placement = $.utmSmoothenedGeometry.map(placement) + $.evalSync = link(_evalSync, [$.sidc, $.properties]) + $.simplifiedGeometry = link(_simplifiedGeometry, [$.geometry, $.centerResolution]) + $.jtsSimplifiedGeometry = $.simplifiedGeometry.ap($.read) + $.lineSmoothing = $.effectiveStyle.map(_lineSmoothing) + $.smoothenedGeometry = link(_smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) + $.jtsSmoothenedGeometry = $.smoothenedGeometry.ap($.read) + $.context = link(_context, [$.jtsSmoothenedGeometry, $.resolution]) + $.placement = $.jtsSmoothenedGeometry.map(placement) + + // ==> Mandatory slots to fill to derive resulting style: + + $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) + $.selection = Signal.link(_selection, [$.selectionMode, $.jtsSimplifiedGeometry]) $.labels = $.parameterizedSIDC - .map(_labels) + .map(_labels(labels)) .ap($.evalSync) .ap($.placement) - $.styles = link((...styles) => styles.reduce(R.concat), [$.labels, $.shape]) + // <== Mandatory slots + + $.styles = link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) return link((styles, styleRegistry, write) => { return styles diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index 3f5ed36b..5950f0fe 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -4,9 +4,11 @@ import * as Geometry from '../../model/geometry' import styleRegistry from './styleRegistry' import point from './point' import polygon from './polygon' +import linestring from './linestring' import defaultStyle from './defaultStyle' import isEqual from 'react-fast-compare' import keyequals from './keyequals' +import transform from './_transform' import colorScheme from './_colorScheme' import schemeStyle from './_schemeStyle' @@ -17,9 +19,14 @@ export default feature => { $.properties = $.feature.map(feature => feature.getProperties()) $.properties.equals = isEqual - $.geometry = $.feature.map(feature => feature.getGeometry()) $.geometry.equals = keyequals() // currently no other way to set equals for map + $.geometryType = $.geometry.map(Geometry.geometryType) + + const [read, write, pointResolution] = transform($.geometry) + $.read = read + $.write = write + $.pointResolution = pointResolution $.sidc = $.properties.map(R.prop('sidc')) $.colorScheme = Signal.link(colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) @@ -30,5 +37,6 @@ export default feature => { const geometryType = Geometry.geometryType(feature.getGeometry()) if (geometryType === 'Point') return point($) else if (geometryType === 'Polygon') return polygon($) + else if (geometryType === 'LineString') return linestring($) else return Signal.of(defaultStyle()) } From 82b64bac2dcedb7a9d77b0bf65e611a3005ffb7e Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 8 Jul 2024 10:13:32 +0200 Subject: [PATCH 38/73] playing with simplier style reduction --- src/renderer/ol/style/linestring.js | 14 ++++---------- src/renderer/ol/style/polygon.js | 12 ++++-------- src/renderer/ol/style/styles.js | 6 ++++++ 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/renderer/ol/style/linestring.js b/src/renderer/ol/style/linestring.js index 6c9c8149..3627624e 100644 --- a/src/renderer/ol/style/linestring.js +++ b/src/renderer/ol/style/linestring.js @@ -4,7 +4,6 @@ import { parameterized } from '../../symbology/2525c' import labels from './linestring-styles/labels' import styles from './linestring-styles/index' import placement from './linestring-styles/placement' -import { styleFactory } from './styleFactory' import _evalSync from './_evalSync' import _smoothenedGeometry from './_smoothenedGeometry' @@ -46,13 +45,8 @@ export default $ => { // <== Mandatory slots - $.styleFactory = Signal.of(styleFactory) - $.styles = link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) - - return link((styles, styleRegistry, write) => { - return styles - .map(styleRegistry) - .map(({ geometry, ...rest }) => ({ geometry: write(geometry), ...rest })) - .flatMap(styleFactory) - }, [$.styles, $.styleRegistry, $.write, $.evalSync]) + return link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) + .ap($.styleRegistryX) + .ap($.rewrite) + .ap($.styleFactory) } diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js index 80f0c4d7..b4c6aed8 100644 --- a/src/renderer/ol/style/polygon.js +++ b/src/renderer/ol/style/polygon.js @@ -46,12 +46,8 @@ export default $ => { // <== Mandatory slots - $.styles = link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) - - return link((styles, styleRegistry, write) => { - return styles - .map(styleRegistry) - .map(({ geometry, ...rest }) => ({ geometry: write(geometry), ...rest })) - .flatMap(styleFactory) - }, [$.styles, $.styleRegistry, $.write, $.evalSync]) + return link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) + .ap($.styleRegistryX) + .ap($.rewrite) + .ap($.styleFactory) } diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index 5950f0fe..17b507f1 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -9,6 +9,7 @@ import defaultStyle from './defaultStyle' import isEqual from 'react-fast-compare' import keyequals from './keyequals' import transform from './_transform' +import { styleFactory } from './styleFactory' import colorScheme from './_colorScheme' import schemeStyle from './_schemeStyle' @@ -26,6 +27,8 @@ export default feature => { const [read, write, pointResolution] = transform($.geometry) $.read = read $.write = write + $.rewrite = write.map(fn => xs => xs.map(({ geometry, ...rest }) => ({ geometry: fn(geometry), ...rest }))) + $.pointResolution = pointResolution $.sidc = $.properties.map(R.prop('sidc')) @@ -33,6 +36,9 @@ export default feature => { $.schemeStyle = Signal.link(schemeStyle, [$.sidc, $.colorScheme]) $.effectiveStyle = Signal.link(effectiveStyle, [$.globalStyle, $.schemeStyle, $.layerStyle, $.featureStyle]) $.styleRegistry = $.effectiveStyle.map(styleRegistry) + $.styleRegistryX = $.styleRegistry.map(fn => xs => xs.map(fn)) + $.styleFactory = Signal.of(xs => xs.flatMap(styleFactory)) + const geometryType = Geometry.geometryType(feature.getGeometry()) if (geometryType === 'Point') return point($) From d45a932dbdc4a1466699c4a180f6c2cd7137b6b2 Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 8 Jul 2024 10:35:24 +0200 Subject: [PATCH 39/73] centralized style reduction. --- src/renderer/ol/style/_rewrite.js | 8 +++++++ src/renderer/ol/style/fallback.js | 13 +++++++++++ src/renderer/ol/style/linestring.js | 6 ----- src/renderer/ol/style/point.js | 12 ++++------ src/renderer/ol/style/polygon.js | 7 ------ src/renderer/ol/style/styles.js | 34 +++++++++++++++-------------- 6 files changed, 43 insertions(+), 37 deletions(-) create mode 100644 src/renderer/ol/style/_rewrite.js create mode 100644 src/renderer/ol/style/fallback.js diff --git a/src/renderer/ol/style/_rewrite.js b/src/renderer/ol/style/_rewrite.js new file mode 100644 index 00000000..838ffc66 --- /dev/null +++ b/src/renderer/ol/style/_rewrite.js @@ -0,0 +1,8 @@ + +/** + * + */ +export default fn => ({ geometry, ...rest }) => + geometry + ? ({ geometry: fn(geometry), ...rest }) + : rest \ No newline at end of file diff --git a/src/renderer/ol/style/fallback.js b/src/renderer/ol/style/fallback.js new file mode 100644 index 00000000..052f8176 --- /dev/null +++ b/src/renderer/ol/style/fallback.js @@ -0,0 +1,13 @@ +import Signal from '@syncpoint/signal' +import defaultStyle from './defaultStyle' + +export default $ => { + + // ==> Mandatory slots to fill to derive resulting style: + + $.labels = Signal.of([]) + $.selection = Signal.of([]) + $.shape = Signal.of([defaultStyle()]) + + // <== Mandatory slots +} diff --git a/src/renderer/ol/style/linestring.js b/src/renderer/ol/style/linestring.js index 3627624e..5694d269 100644 --- a/src/renderer/ol/style/linestring.js +++ b/src/renderer/ol/style/linestring.js @@ -1,4 +1,3 @@ -import * as R from 'ramda' import Signal from '@syncpoint/signal' import { parameterized } from '../../symbology/2525c' import labels from './linestring-styles/labels' @@ -44,9 +43,4 @@ export default $ => { .ap($.placement) // <== Mandatory slots - - return link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) - .ap($.styleRegistryX) - .ap($.rewrite) - .ap($.styleFactory) } diff --git a/src/renderer/ol/style/point.js b/src/renderer/ol/style/point.js index d34e6a3c..6c2d638d 100644 --- a/src/renderer/ol/style/point.js +++ b/src/renderer/ol/style/point.js @@ -1,7 +1,6 @@ import * as R from 'ramda' import Signal from '@syncpoint/signal' import { MODIFIERS } from '../../symbology/2525c' -import { styleFactory } from './styleFactory' /** * @@ -17,10 +16,7 @@ export default $ => { $.labels = Signal.of([]) $.selection = Signal.of([]) - - // <== Mandatory slots - - return Signal.link((properties, geometry, styleRegistry) => { + $.shape = Signal.link((properties, geometry) => { const sidc = properties.sidc const modifiers = Object.entries(properties) @@ -33,7 +29,7 @@ export default $ => { 'symbol-code': sidc, 'symbol-modifiers': modifiers }] - .map(styleRegistry) - .flatMap(styleFactory) - }, [$.properties, $.geometry, $.styleRegistry]) + }, [$.properties, $.jtsSimplifiedGeometry]) + + // <== Mandatory slots } diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js index b4c6aed8..8cf021f4 100644 --- a/src/renderer/ol/style/polygon.js +++ b/src/renderer/ol/style/polygon.js @@ -1,10 +1,8 @@ -import * as R from 'ramda' import Signal from '@syncpoint/signal' import { parameterized } from '../../symbology/2525c' import labels from './polygon-styles/labels' import styles from './polygon-styles/index' import placement from './polygon-styles/placement' -import { styleFactory } from './styleFactory' import _evalSync from './_evalSync' import _smoothenedGeometry from './_smoothenedGeometry' @@ -45,9 +43,4 @@ export default $ => { .ap($.placement) // <== Mandatory slots - - return link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) - .ap($.styleRegistryX) - .ap($.rewrite) - .ap($.styleFactory) } diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index 17b507f1..80148c9e 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -5,15 +5,16 @@ import styleRegistry from './styleRegistry' import point from './point' import polygon from './polygon' import linestring from './linestring' -import defaultStyle from './defaultStyle' +import fallback from './fallback' import isEqual from 'react-fast-compare' import keyequals from './keyequals' import transform from './_transform' import { styleFactory } from './styleFactory' -import colorScheme from './_colorScheme' -import schemeStyle from './_schemeStyle' -import effectiveStyle from './_effectiveStyle' +import _colorScheme from './_colorScheme' +import _schemeStyle from './_schemeStyle' +import _effectiveStyle from './_effectiveStyle' +import _rewrite from './_rewrite' export default feature => { const { $ } = feature @@ -26,23 +27,24 @@ export default feature => { const [read, write, pointResolution] = transform($.geometry) $.read = read - $.write = write - $.rewrite = write.map(fn => xs => xs.map(({ geometry, ...rest }) => ({ geometry: fn(geometry), ...rest }))) - + $.rewrite = write.map(fn => xs => xs.map(_rewrite(fn))) $.pointResolution = pointResolution - $.sidc = $.properties.map(R.prop('sidc')) - $.colorScheme = Signal.link(colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) - $.schemeStyle = Signal.link(schemeStyle, [$.sidc, $.colorScheme]) - $.effectiveStyle = Signal.link(effectiveStyle, [$.globalStyle, $.schemeStyle, $.layerStyle, $.featureStyle]) + $.colorScheme = Signal.link(_colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) + $.schemeStyle = Signal.link(_schemeStyle, [$.sidc, $.colorScheme]) + $.effectiveStyle = Signal.link(_effectiveStyle, [$.globalStyle, $.schemeStyle, $.layerStyle, $.featureStyle]) $.styleRegistry = $.effectiveStyle.map(styleRegistry) $.styleRegistryX = $.styleRegistry.map(fn => xs => xs.map(fn)) $.styleFactory = Signal.of(xs => xs.flatMap(styleFactory)) - const geometryType = Geometry.geometryType(feature.getGeometry()) - if (geometryType === 'Point') return point($) - else if (geometryType === 'Polygon') return polygon($) - else if (geometryType === 'LineString') return linestring($) - else return Signal.of(defaultStyle()) + if (geometryType === 'Point') point($) + else if (geometryType === 'Polygon') polygon($) + else if (geometryType === 'LineString') linestring($) + else fallback($) + + return Signal.link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) + .ap($.styleRegistryX) + .ap($.rewrite) + .ap($.styleFactory) } From cc0857661c577c7a95c704b7928b2a8314702077 Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 8 Jul 2024 11:57:55 +0200 Subject: [PATCH 40/73] clean-up: reduced duplicate code. --- src/renderer/ol/style/fallback.js | 2 +- src/renderer/ol/style/linestring.js | 23 +++++------------------ src/renderer/ol/style/point.js | 2 +- src/renderer/ol/style/polygon.js | 23 +++++------------------ src/renderer/ol/style/styles.js | 13 ++++++++++--- 5 files changed, 22 insertions(+), 41 deletions(-) diff --git a/src/renderer/ol/style/fallback.js b/src/renderer/ol/style/fallback.js index 052f8176..4381b880 100644 --- a/src/renderer/ol/style/fallback.js +++ b/src/renderer/ol/style/fallback.js @@ -3,7 +3,7 @@ import defaultStyle from './defaultStyle' export default $ => { - // ==> Mandatory slots to fill to derive resulting style: + // ==> Mandatory slots to derive resulting style: $.labels = Signal.of([]) $.selection = Signal.of([]) diff --git a/src/renderer/ol/style/linestring.js b/src/renderer/ol/style/linestring.js index 5694d269..282784b3 100644 --- a/src/renderer/ol/style/linestring.js +++ b/src/renderer/ol/style/linestring.js @@ -1,39 +1,26 @@ import Signal from '@syncpoint/signal' -import { parameterized } from '../../symbology/2525c' import labels from './linestring-styles/labels' import styles from './linestring-styles/index' import placement from './linestring-styles/placement' -import _evalSync from './_evalSync' import _smoothenedGeometry from './_smoothenedGeometry' +import _simplifiedGeometry from './_simplifiedGeometry' import _context from './_context' import _labels from './_labels' import _shape from './_shape' import _lineSmoothing from './_lineSmoothing' import _selection from './_selection' -const _simplifiedGeometry = (geometry, resolution) => { - const coordinates = geometry.getCoordinates() - return coordinates.length > 50 - ? geometry.simplify(resolution) - : geometry -} - export default $ => { - const { link } = Signal - - $.resolution = $.centerResolution.ap($.pointResolution) - $.parameterizedSIDC = $.sidc.map(parameterized) - $.evalSync = link(_evalSync, [$.sidc, $.properties]) - $.simplifiedGeometry = link(_simplifiedGeometry, [$.geometry, $.centerResolution]) + $.simplifiedGeometry = Signal.link(_simplifiedGeometry, [$.geometry, $.centerResolution]) $.jtsSimplifiedGeometry = $.simplifiedGeometry.ap($.read) $.lineSmoothing = $.effectiveStyle.map(_lineSmoothing) - $.smoothenedGeometry = link(_smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) + $.smoothenedGeometry = Signal.link(_smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) $.jtsSmoothenedGeometry = $.smoothenedGeometry.ap($.read) - $.context = link(_context, [$.jtsSmoothenedGeometry, $.resolution]) + $.context = Signal.link(_context, [$.jtsSmoothenedGeometry, $.resolution]) $.placement = $.jtsSmoothenedGeometry.map(placement) - // ==> Mandatory slots to fill to derive resulting style: + // ==> Mandatory slots to derive resulting style: $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) $.selection = Signal.link(_selection, [$.selectionMode, $.jtsSimplifiedGeometry]) diff --git a/src/renderer/ol/style/point.js b/src/renderer/ol/style/point.js index 6c2d638d..7bdaec2d 100644 --- a/src/renderer/ol/style/point.js +++ b/src/renderer/ol/style/point.js @@ -12,7 +12,7 @@ export default $ => { // $.jtsSimplifiedGeometry = $.geometry.ap($.read) - // ==> Mandatory slots to fill to derive resulting style: + // ==> Mandatory slots to derive resulting style: $.labels = Signal.of([]) $.selection = Signal.of([]) diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js index 8cf021f4..f0e2d1bc 100644 --- a/src/renderer/ol/style/polygon.js +++ b/src/renderer/ol/style/polygon.js @@ -1,39 +1,26 @@ import Signal from '@syncpoint/signal' -import { parameterized } from '../../symbology/2525c' import labels from './polygon-styles/labels' import styles from './polygon-styles/index' import placement from './polygon-styles/placement' -import _evalSync from './_evalSync' import _smoothenedGeometry from './_smoothenedGeometry' +import _simplifiedGeometry from './_simplifiedGeometry' import _context from './_context' import _labels from './_labels' import _shape from './_shape' import _lineSmoothing from './_lineSmoothing' import _selection from './_selection' -const _simplifiedGeometry = (geometry, resolution) => { - const coordinates = geometry.getCoordinates() - return coordinates[0].length > 50 - ? geometry.simplify(resolution) - : geometry -} - export default $ => { - const { link } = Signal - - $.resolution = $.centerResolution.ap($.pointResolution) - $.parameterizedSIDC = $.sidc.map(parameterized) - $.evalSync = link(_evalSync, [$.sidc, $.properties]) - $.simplifiedGeometry = link(_simplifiedGeometry, [$.geometry, $.centerResolution]) + $.simplifiedGeometry = Signal.link(_simplifiedGeometry, [$.geometry, $.centerResolution]) $.jtsSimplifiedGeometry = $.simplifiedGeometry.ap($.read) $.lineSmoothing = $.effectiveStyle.map(_lineSmoothing) - $.smoothenedGeometry = link(_smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) + $.smoothenedGeometry = Signal.link(_smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) $.jtsSmoothenedGeometry = $.smoothenedGeometry.ap($.read) - $.context = link(_context, [$.jtsSmoothenedGeometry, $.resolution]) + $.context = Signal.link(_context, [$.jtsSmoothenedGeometry, $.resolution]) $.placement = $.jtsSmoothenedGeometry.map(placement) - // ==> Mandatory slots to fill to derive resulting style: + // ==> Mandatory slots to derive resulting style: $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) $.selection = Signal.link(_selection, [$.selectionMode, $.jtsSimplifiedGeometry]) diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index 80148c9e..e495f62a 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -1,4 +1,5 @@ import * as R from 'ramda' +import { parameterized } from '../../symbology/2525c' import Signal from '@syncpoint/signal' import * as Geometry from '../../model/geometry' import styleRegistry from './styleRegistry' @@ -15,6 +16,7 @@ import _colorScheme from './_colorScheme' import _schemeStyle from './_schemeStyle' import _effectiveStyle from './_effectiveStyle' import _rewrite from './_rewrite' +import _evalSync from './_evalSync' export default feature => { const { $ } = feature @@ -24,17 +26,22 @@ export default feature => { $.geometry = $.feature.map(feature => feature.getGeometry()) $.geometry.equals = keyequals() // currently no other way to set equals for map $.geometryType = $.geometry.map(Geometry.geometryType) + $.evalSync = Signal.link(_evalSync, [$.sidc, $.properties]) const [read, write, pointResolution] = transform($.geometry) $.read = read $.rewrite = write.map(fn => xs => xs.map(_rewrite(fn))) $.pointResolution = pointResolution + $.resolution = $.centerResolution.ap($.pointResolution) + $.sidc = $.properties.map(R.prop('sidc')) + $.parameterizedSIDC = $.sidc.map(parameterized) $.colorScheme = Signal.link(_colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) $.schemeStyle = Signal.link(_schemeStyle, [$.sidc, $.colorScheme]) $.effectiveStyle = Signal.link(_effectiveStyle, [$.globalStyle, $.schemeStyle, $.layerStyle, $.featureStyle]) - $.styleRegistry = $.effectiveStyle.map(styleRegistry) - $.styleRegistryX = $.styleRegistry.map(fn => xs => xs.map(fn)) + $.styleRegistry = $.effectiveStyle + .map(styleRegistry) + .map(fn => xs => xs.map(fn)) $.styleFactory = Signal.of(xs => xs.flatMap(styleFactory)) const geometryType = Geometry.geometryType(feature.getGeometry()) @@ -44,7 +51,7 @@ export default feature => { else fallback($) return Signal.link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) - .ap($.styleRegistryX) + .ap($.styleRegistry) .ap($.rewrite) .ap($.styleFactory) } From 33789f0e3ec7ffd13e4dbdd3e8df3403063e539b Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 8 Jul 2024 13:02:10 +0200 Subject: [PATCH 41/73] support MutiPoint styles. --- src/renderer/ol/style/_placement.js | 30 -------------------------- src/renderer/ol/style/multipoint.js | 33 +++++++++++++++++++++++++++++ src/renderer/ol/style/styles.js | 2 ++ 3 files changed, 35 insertions(+), 30 deletions(-) delete mode 100644 src/renderer/ol/style/_placement.js create mode 100644 src/renderer/ol/style/multipoint.js diff --git a/src/renderer/ol/style/_placement.js b/src/renderer/ol/style/_placement.js deleted file mode 100644 index 5120007d..00000000 --- a/src/renderer/ol/style/_placement.js +++ /dev/null @@ -1,30 +0,0 @@ -import * as R from 'ramda' -import * as TS from '../ts' -import Polygon from './polygon-styles/placement' -import LineString from './linestring-styles/placement' - -const PLACEMENT = { - Polygon, - LineString, - MultiPoint: Polygon -} - -const pointBuffer = geometry => { - const [C, A] = TS.coordinates(geometry) - const segment = TS.segment([C, A]) - return TS.pointBuffer(TS.point(C))(segment.getLength()) - -} - -export default $ => { - const placement = $.geometryType.map(geometryType => PLACEMENT[geometryType] || R.identity) - const geometry = $.geometryType.chain(geometryType => { - return R.cond([ - [R.equals('LineString'), R.always($.jtsSmoothenedGeometry)], - [R.equals('Polygon'), R.always($.jtsSmoothenedGeometry)], - [R.equals('MultiPoint'), R.always($.geometry.ap($.read).map(pointBuffer))] - ])(geometryType) - }) - - return geometry.ap(placement) -} diff --git a/src/renderer/ol/style/multipoint.js b/src/renderer/ol/style/multipoint.js new file mode 100644 index 00000000..7c486bd4 --- /dev/null +++ b/src/renderer/ol/style/multipoint.js @@ -0,0 +1,33 @@ +import Signal from '@syncpoint/signal' +import * as TS from '../ts' +import labels from './multipoint-styles/labels' +import styles from './multipoint-styles/index' +import placement from './polygon-styles/placement' + +import _context from './_context' +import _labels from './_labels' +import _shape from './_shape' +import _selection from './_selection' + +const _pointBuffer = geometry => { + const [C, A] = TS.coordinates(geometry) + const segment = TS.segment([C, A]) + return TS.pointBuffer(TS.point(C))(segment.getLength()) +} + +export default $ => { + $.jtsGeometry = $.geometry.ap($.read) + $.context = Signal.link(_context, [$.jtsGeometry, $.resolution]) + $.placement = $.jtsGeometry.map(_pointBuffer).map(placement) + + // ==> Mandatory slots to derive resulting style: + + $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) + $.selection = Signal.link(_selection, [$.selectionMode, $.jtsGeometry]) + $.labels = $.parameterizedSIDC + .map(_labels(labels)) + .ap($.evalSync) + .ap($.placement) + + // <== Mandatory slots +} diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index e495f62a..e53c58fd 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -6,6 +6,7 @@ import styleRegistry from './styleRegistry' import point from './point' import polygon from './polygon' import linestring from './linestring' +import multipoint from './multipoint' import fallback from './fallback' import isEqual from 'react-fast-compare' import keyequals from './keyequals' @@ -48,6 +49,7 @@ export default feature => { if (geometryType === 'Point') point($) else if (geometryType === 'Polygon') polygon($) else if (geometryType === 'LineString') linestring($) + else if (geometryType === 'MultiPoint') multipoint($) else fallback($) return Signal.link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) From af478553148f48d67998845322c299b034f56473 Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 8 Jul 2024 23:26:18 +0200 Subject: [PATCH 42/73] support Corridor styles. --- src/renderer/model/sources/featureSource.js | 7 ++++++- src/renderer/ol/style/_shape.js | 7 ++++++- src/renderer/ol/style/corridor.js | 19 +++++++++++++++++++ src/renderer/ol/style/styles.js | 2 ++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 src/renderer/ol/style/corridor.js diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index a480a579..eb03a95d 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -155,7 +155,12 @@ export const featureSource = services => { ? 'multiselect' : 'singleselect' - const apply = mode => feature => feature.$.selectionMode(mode) + const apply = mode => feature => { + // FIXME: know sure why this may happen, but it does. + if (!feature || !feature.$) return + feature.$.selectionMode(mode) + } + deselected.map(source.getFeatureById.bind(source)).map(apply('default')) selection.selected().map(source.getFeatureById.bind(source)).map(apply(mode)) }) diff --git a/src/renderer/ol/style/_shape.js b/src/renderer/ol/style/_shape.js index 10131abf..c9644d49 100644 --- a/src/renderer/ol/style/_shape.js +++ b/src/renderer/ol/style/_shape.js @@ -1,5 +1,10 @@ +import * as R from 'ramda' /** * */ -export default styles => sidc => styles[sidc] || styles.DEFAULT +export default styles => sidc => { + const tryer = (styles[sidc] || styles.DEFAULT) + const catcher = (_, context) => [{ id: 'style:wasp-stroke', geometry: context.geometry }] + return R.tryCatch(tryer, catcher) +} diff --git a/src/renderer/ol/style/corridor.js b/src/renderer/ol/style/corridor.js new file mode 100644 index 00000000..8d8b923a --- /dev/null +++ b/src/renderer/ol/style/corridor.js @@ -0,0 +1,19 @@ +import Signal from '@syncpoint/signal' +import styles from './corridor-styles/index' + +import _context from './_context' +import _shape from './_shape' +import _selection from './_selection' + +export default $ => { + $.jtsGeometry = $.geometry.ap($.read) + $.context = Signal.link(_context, [$.jtsGeometry, $.resolution]) + + // ==> Mandatory slots to derive resulting style: + + $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) + $.selection = Signal.link(_selection, [$.selectionMode, $.jtsGeometry]) + $.labels = Signal.of([]) + + // <== Mandatory slots +} diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index e53c58fd..b194e58e 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -7,6 +7,7 @@ import point from './point' import polygon from './polygon' import linestring from './linestring' import multipoint from './multipoint' +import corridor from './corridor' import fallback from './fallback' import isEqual from 'react-fast-compare' import keyequals from './keyequals' @@ -50,6 +51,7 @@ export default feature => { else if (geometryType === 'Polygon') polygon($) else if (geometryType === 'LineString') linestring($) else if (geometryType === 'MultiPoint') multipoint($) + else if (geometryType === 'LineString:Point') corridor($) else fallback($) return Signal.link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) From e93bd23f6b4153cfe199fc3a3db3a24015705084 Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 9 Jul 2024 08:23:26 +0200 Subject: [PATCH 43/73] label clipping. --- src/renderer/ol/style/_bbox.js | 95 +++++++++++++++++++++++++++++ src/renderer/ol/style/_clip.js | 23 +++++++ src/renderer/ol/style/linestring.js | 1 - src/renderer/ol/style/multipoint.js | 1 - src/renderer/ol/style/polygon.js | 1 - src/renderer/ol/style/styles.js | 15 +++-- 6 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 src/renderer/ol/style/_bbox.js create mode 100644 src/renderer/ol/style/_clip.js diff --git a/src/renderer/ol/style/_bbox.js b/src/renderer/ol/style/_bbox.js new file mode 100644 index 00000000..1cb04a32 --- /dev/null +++ b/src/renderer/ol/style/_bbox.js @@ -0,0 +1,95 @@ +import * as TS from '../ts' +import { PI_OVER_2 } from '../../../shared/Math' + +const canvas = document.createElement('canvas') +const context = canvas.getContext('2d') + +/** + * + */ +const textBoundingBox = (resolution, style) => { + const textField = style['text-field'] + if (!textField) return null + if (style['text-clipping'] === 'none') return null + + // Prepare bounding box geometry (dimensions only, including padding). + const lines = textField.split('\n') + const [maxWidthPx, maxHeightPx] = lines.reduce((acc, line) => { + context.font = style['text-font'] + const metrics = context.measureText(line) + const width = metrics.width + const height = 1.2 * lines.length * ((metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent)) + if (width > acc[0]) acc[0] = width + if (height > acc[1]) acc[1] = height + return acc + }, [0, 0]) + + const { x, y } = style.geometry.getCoordinates()[0] + const padding = style['text-padding'] || 0 + const dx = (maxWidthPx / 2 + padding) * resolution + const dy = (maxHeightPx / 2 + padding) * resolution + + const x1 = x - dx + const x2 = x + dx + const y1 = y - dy + const y2 = y + dy + const points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]] + const geometry = TS.polygon(points.map(TS.coordinate)) + + // Transform geometry (rotate/translate) to match + // label options offset, justify and rotate. + + const rotate = style['text-rotate'] || 0 + const justify = style['text-justify'] || 'center' + const [offsetX, offsetY] = style['text-offset'] || [0, 0] + + const flipX = { start: -1, end: 1, center: 0 } + const flipY = rotate < -PI_OVER_2 || rotate > PI_OVER_2 ? -1 : 1 + const tx = (-offsetX + flipX[justify] * (maxWidthPx / 2)) * resolution + const ty = flipY * offsetY * resolution + + const theta = 2 * Math.PI - rotate + const at = TS.AffineTransformation.translationInstance(-(x + tx), -(y + ty)) + at.rotate(theta) + at.translate(x, y) + + return at.transform(geometry) +} + + +/** + * + */ +const iconBoundingBox = (resolution, style) => { + const scale = style['icon-scale'] + if (!scale) return null + + const width = style['icon-width'] * scale / 4 + const height = style['icon-height'] * scale / 4 + const rotate = style['icon-rotate'] || 0 + const padding = style['icon-padding'] || 0 + const { x, y } = style.geometry.getCoordinates()[0] + + const x1 = x - (width + padding) * resolution + const x2 = x + (width + padding) * resolution + const y1 = y - (height + padding) * resolution + const y2 = y + (height + padding) * resolution + const points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]] + const theta = 2 * Math.PI - rotate + const geometry = TS.polygon(points.map(TS.coordinate)) + const rotation = TS.AffineTransformation.rotationInstance(theta, x, y) + return rotation.transform(geometry) +} + +const bbox = resolution => style => { + if (style['text-field']) return textBoundingBox(resolution, style) + else if (style['icon-image']) return iconBoundingBox(resolution, style) + else return null +} + +/** + * + */ +export default (resolution, styles) => styles + .map(bbox(resolution)) + .filter(Boolean) diff --git a/src/renderer/ol/style/_clip.js b/src/renderer/ol/style/_clip.js new file mode 100644 index 00000000..4e6eeae4 --- /dev/null +++ b/src/renderer/ol/style/_clip.js @@ -0,0 +1,23 @@ +import * as TS from '../ts' +import _bboxes from './_bbox' + +/** + * + */ +export default resolution => { + return styles => { + const bboxes = _bboxes(resolution, styles) + if (bboxes.length === 0) return styles + + const clip = geometry => TS.difference([geometry, ...bboxes]) + const lineString = geometry => TS.lineString(geometry.getCoordinates()) + const geometry = styles.some(props => props['text-clipping'] === 'line') + ? lineString(styles[0].geometry) + : styles[0].geometry + + // Replace primary geometry with clipped geometry: + styles[0].geometry = clip(geometry) + + return styles + } +} diff --git a/src/renderer/ol/style/linestring.js b/src/renderer/ol/style/linestring.js index 282784b3..074c1ee7 100644 --- a/src/renderer/ol/style/linestring.js +++ b/src/renderer/ol/style/linestring.js @@ -26,7 +26,6 @@ export default $ => { $.selection = Signal.link(_selection, [$.selectionMode, $.jtsSimplifiedGeometry]) $.labels = $.parameterizedSIDC .map(_labels(labels)) - .ap($.evalSync) .ap($.placement) // <== Mandatory slots diff --git a/src/renderer/ol/style/multipoint.js b/src/renderer/ol/style/multipoint.js index 7c486bd4..381c70d6 100644 --- a/src/renderer/ol/style/multipoint.js +++ b/src/renderer/ol/style/multipoint.js @@ -26,7 +26,6 @@ export default $ => { $.selection = Signal.link(_selection, [$.selectionMode, $.jtsGeometry]) $.labels = $.parameterizedSIDC .map(_labels(labels)) - .ap($.evalSync) .ap($.placement) // <== Mandatory slots diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js index f0e2d1bc..b1520b94 100644 --- a/src/renderer/ol/style/polygon.js +++ b/src/renderer/ol/style/polygon.js @@ -26,7 +26,6 @@ export default $ => { $.selection = Signal.link(_selection, [$.selectionMode, $.jtsSimplifiedGeometry]) $.labels = $.parameterizedSIDC .map(_labels(labels)) - .ap($.evalSync) .ap($.placement) // <== Mandatory slots diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index b194e58e..c3c9e24c 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -19,16 +19,18 @@ import _schemeStyle from './_schemeStyle' import _effectiveStyle from './_effectiveStyle' import _rewrite from './_rewrite' import _evalSync from './_evalSync' +import _clip from './_clip' export default feature => { const { $ } = feature $.properties = $.feature.map(feature => feature.getProperties()) $.properties.equals = isEqual + $.sidc = $.properties.map(R.prop('sidc')) + $.parameterizedSIDC = $.sidc.map(parameterized) $.geometry = $.feature.map(feature => feature.getGeometry()) $.geometry.equals = keyequals() // currently no other way to set equals for map $.geometryType = $.geometry.map(Geometry.geometryType) - $.evalSync = Signal.link(_evalSync, [$.sidc, $.properties]) const [read, write, pointResolution] = transform($.geometry) $.read = read @@ -36,8 +38,7 @@ export default feature => { $.pointResolution = pointResolution $.resolution = $.centerResolution.ap($.pointResolution) - $.sidc = $.properties.map(R.prop('sidc')) - $.parameterizedSIDC = $.sidc.map(parameterized) + $.evalSync = Signal.link(_evalSync, [$.sidc, $.properties]) $.colorScheme = Signal.link(_colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) $.schemeStyle = Signal.link(_schemeStyle, [$.sidc, $.colorScheme]) $.effectiveStyle = Signal.link(_effectiveStyle, [$.globalStyle, $.schemeStyle, $.layerStyle, $.featureStyle]) @@ -54,8 +55,14 @@ export default feature => { else if (geometryType === 'LineString:Point') corridor($) else fallback($) - return Signal.link((...styles) => styles.reduce(R.concat), [$.labels, $.shape, $.selection]) + // Primary shape style must go first because of clipping: + $.styles = Signal.link((...styles) => styles.reduce(R.concat), [$.shape, $.labels, $.selection]) + $.clip = $.resolution.map(_clip) + + return $.styles .ap($.styleRegistry) + .ap($.evalSync) + .ap($.clip) .ap($.rewrite) .ap($.styleFactory) } From 979cd3bf6ca376031a0e8f803597e86545b2c6ce Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 9 Jul 2024 10:44:19 +0200 Subject: [PATCH 44/73] update feature (style) from database events. --- src/renderer/model/sources/featureSource.js | 17 ++- src/renderer/ol/style/_evalSync.js | 35 +++++- src/renderer/ol/style/keyequals.js | 3 +- src/renderer/ol/style/labels.js | 123 -------------------- src/renderer/ol/style/styles.js | 6 - src/shared/signal.js | 12 +- 6 files changed, 55 insertions(+), 141 deletions(-) delete mode 100644 src/renderer/ol/style/labels.js diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index eb03a95d..f5a90da4 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -4,9 +4,10 @@ import VectorSource from 'ol/source/Vector' import GeoJSON from 'ol/format/GeoJSON' import * as ID from '../../ids' import styles from '../../ol/style/styles' -import { flatten, select } from '../../../shared/signal' +import { flat, select } from '../../../shared/signal' import { setCoordinates } from '../geometry' import keyequals from '../../ol/style/keyequals' +import isEqual from 'react-fast-compare' const format = new GeoJSON({ dataProjection: 'EPSG:3857', @@ -21,6 +22,7 @@ const readFeature = R.curry((state, source) => { const feature = format.readFeature(source) const featureId = feature.getId() const layerId = ID.layerId(featureId) + const { geometry, ...properties } = feature.getProperties() feature.$ = { @@ -31,7 +33,8 @@ const readFeature = R.curry((state, source) => { // new signals which conversely are only updated when this information // has actually changed. // - feature: Signal.of(feature, { equals: keyequals() }), + properties: Signal.of(properties, { equals: isEqual }), + geometry: Signal.of(geometry, { equals: keyequals() }), globalStyle: Signal.of(state.styles[ID.defaultStyleId]), layerStyle: Signal.of(state.styles[ID.styleId(layerId)] ?? {}), featureStyle: Signal.of(state.styles[ID.styleId(featureId)] ?? {}), @@ -60,7 +63,7 @@ const readFeature = R.curry((state, source) => { } feature.on('change', ({ target }) => { - target.$.feature(target) + target.$.geometry(target.getGeometry()) }) return feature @@ -83,7 +86,7 @@ const ord = R.cond([ const isCandidateId = id => ID.isFeatureId(id) || ID.isMarkerId(id) || ID.isMeasureId(id) const operations = R.compose( - flatten, + flat, R.map(R.sort((a, b) => ord(a) - ord(b))), R.map(R.prop('operations')) ) @@ -133,6 +136,7 @@ export const featureSource = services => { layerStyle.on(({ type, key, value }) => { const layerId = ID.layerId(key) + if (type === 'del') delete state.styles[key] source.getFeatures() .filter(feature => ID.layerId(feature.getId()) === layerId) .forEach(feature => feature.$.layerStyle(type === 'put' ? value : {})) @@ -141,11 +145,14 @@ export const featureSource = services => { featureStyle.on(({ type, key, value }) => { const featureId = ID.featureId(key) const feature = source.getFeatureById(featureId) + if (type === 'del') delete state.styles[key] if (feature) feature.$.featureStyle(type === 'put' ? value : {}) }) feature.on(({ type, key, value }) => { - // TODO: ... + const feature = source.getFeatureById(key) + if (type === 'del') source.removeFeature(feature) + else feature.$.properties(value.properties) }) // <== batch event handling diff --git a/src/renderer/ol/style/_evalSync.js b/src/renderer/ol/style/_evalSync.js index f8d98635..227ff98a 100644 --- a/src/renderer/ol/style/_evalSync.js +++ b/src/renderer/ol/style/_evalSync.js @@ -1,6 +1,39 @@ import { echelonCode } from '../../symbology/2525c' import { echelons } from './echelon' -import { evalSync } from './labels' +import { Jexl } from 'jexl' + + +const jexl = new Jexl() + +/** + * + */ +const evalSync = context => { + + const evalSync = textField => Array.isArray(textField) + ? textField.map(evalSync).filter(Boolean).join('\n') + : jexl.evalSync(textField, context) + + const replaceOne = properties => { + properties = Array.isArray(properties) ? properties : [properties] + return properties.reduce((acc, spec) => { + if (!spec['text-field']) acc.push(spec) + else { + const textField = evalSync(spec['text-field']) + if (textField) acc.push({ ...spec, 'text-field': textField }) + } + + return acc + }, []) + } + + const replaceAll = arg => { + if (!Array.isArray(arg)) return replaceAll(arg) + return arg.flatMap(replaceOne) + } + + return replaceAll +} export default (sidc, properties) => { const sizeCode = echelonCode(sidc) diff --git a/src/renderer/ol/style/keyequals.js b/src/renderer/ol/style/keyequals.js index ecb11407..0d845a9d 100644 --- a/src/renderer/ol/style/keyequals.js +++ b/src/renderer/ol/style/keyequals.js @@ -6,7 +6,8 @@ const keyequals = () => { let last return (_, b) => { if (last === key(b)) return true - else last = key(b); return false + else last = key(b) + return false } } diff --git a/src/renderer/ol/style/labels.js b/src/renderer/ol/style/labels.js deleted file mode 100644 index f89c6ca5..00000000 --- a/src/renderer/ol/style/labels.js +++ /dev/null @@ -1,123 +0,0 @@ -import * as R from 'ramda' -import { Jexl } from 'jexl' -const canvas = document.createElement('canvas') -const context = canvas.getContext('2d') - -/** - * - */ -const textBoundingBox = ({ TS, PI_OVER_2, resolution }, props) => { - const textField = props['text-field'] - if (!textField) return null - if (props['text-clipping'] === 'none') return null - - // Prepare bounding box geometry (dimensions only, including padding). - const lines = textField.split('\n') - const [maxWidthPx, maxHeightPx] = lines.reduce((acc, line) => { - context.font = props['text-font'] - const metrics = context.measureText(line) - const width = metrics.width - const height = 1.2 * lines.length * ((metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent)) - if (width > acc[0]) acc[0] = width - if (height > acc[1]) acc[1] = height - return acc - }, [0, 0]) - - const { x, y } = props.geometry.getCoordinates()[0] - const padding = props['text-padding'] || 0 - const dx = (maxWidthPx / 2 + padding) * resolution - const dy = (maxHeightPx / 2 + padding) * resolution - - const x1 = x - dx - const x2 = x + dx - const y1 = y - dy - const y2 = y + dy - const points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]] - const geometry = TS.polygon(points.map(TS.coordinate)) - - // Transform geometry (rotate/translate) to match - // label options offset, justify and rotate. - - const rotate = props['text-rotate'] || 0 - const justify = props['text-justify'] || 'center' - const [offsetX, offsetY] = props['text-offset'] || [0, 0] - - const flipX = { start: -1, end: 1, center: 0 } - const flipY = rotate < -PI_OVER_2 || rotate > PI_OVER_2 ? -1 : 1 - const tx = (-offsetX + flipX[justify] * (maxWidthPx / 2)) * resolution - const ty = flipY * offsetY * resolution - - const theta = 2 * Math.PI - rotate - const at = TS.AffineTransformation.translationInstance(-(x + tx), -(y + ty)) - at.rotate(theta) - at.translate(x, y) - - return at.transform(geometry) -} - - -/** - * - */ -const iconBoundingBox = ({ TS, resolution }, props) => { - const scale = props['icon-scale'] - if (!scale) return null - - const width = props['icon-width'] * scale / 4 - const height = props['icon-height'] * scale / 4 - const rotate = props['icon-rotate'] || 0 - const padding = props['icon-padding'] || 0 - const { x, y } = props.geometry.getCoordinates()[0] - - const x1 = x - (width + padding) * resolution - const x2 = x + (width + padding) * resolution - const y1 = y - (height + padding) * resolution - const y2 = y + (height + padding) * resolution - const points = [[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]] - const theta = 2 * Math.PI - rotate - const geometry = TS.polygon(points.map(TS.coordinate)) - const rotation = TS.AffineTransformation.rotationInstance(theta, x, y) - return rotation.transform(geometry) -} - - -/** - * - */ -export const boundingBox = R.curry((context, style) => { - if (style['text-field']) return textBoundingBox(context, style) - else if (style['icon-image']) return iconBoundingBox(context, style) - else return null -}) - -const jexl = new Jexl() - -/** - * - */ -export const evalSync = context => { - - const evalSync = textField => Array.isArray(textField) - ? textField.map(evalSync).filter(Boolean).join('\n') - : jexl.evalSync(textField, context) - - const replaceOne = properties => { - properties = Array.isArray(properties) ? properties : [properties] - return properties.reduce((acc, spec) => { - if (!spec['text-field']) acc.push(spec) - else { - const textField = evalSync(spec['text-field']) - if (textField) acc.push({ ...spec, 'text-field': textField }) - } - - return acc - }, []) - } - - const replaceAll = arg => { - if (!Array.isArray(arg)) return replaceAll(arg) - return arg.flatMap(replaceOne) - } - - return replaceAll -} diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index c3c9e24c..b3f28731 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -9,8 +9,6 @@ import linestring from './linestring' import multipoint from './multipoint' import corridor from './corridor' import fallback from './fallback' -import isEqual from 'react-fast-compare' -import keyequals from './keyequals' import transform from './_transform' import { styleFactory } from './styleFactory' @@ -24,12 +22,8 @@ import _clip from './_clip' export default feature => { const { $ } = feature - $.properties = $.feature.map(feature => feature.getProperties()) - $.properties.equals = isEqual $.sidc = $.properties.map(R.prop('sidc')) $.parameterizedSIDC = $.sidc.map(parameterized) - $.geometry = $.feature.map(feature => feature.getGeometry()) - $.geometry.equals = keyequals() // currently no other way to set equals for map $.geometryType = $.geometry.map(Geometry.geometryType) const [read, write, pointResolution] = transform($.geometry) diff --git a/src/shared/signal.js b/src/shared/signal.js index 94421ce0..5450ca6e 100644 --- a/src/shared/signal.js +++ b/src/shared/signal.js @@ -6,20 +6,20 @@ import Signal from '@syncpoint/signal' /** * select :: Signal S => [a -> Boolean] -> S a -> [S a] * - * Split one input signal into multiply output signals based on conditions. + * Split one input signal into multiply output signals based on predicates. * Each input value is either forwarded to one output signal or dropped if * no condition matches. */ -export const select = R.curry((conditions, signal) => { - const outputs = conditions.map(() => Signal.of()) +export const select = R.curry((predicates, signal) => { + const outputs = predicates.map(() => Signal.of()) signal.on(value => { const match = condition => condition(value) - outputs[conditions.findIndex(match)]?.(value) + outputs[predicates.findIndex(match)]?.(value) }) return outputs }) -export const flatten = signal => { +export const flat = signal => { const output = Signal.of() signal.on(v => (Array.isArray(v) ? v : [v]).forEach(output)) return output @@ -38,6 +38,8 @@ export const split = R.curry((fns, signal) => { return outputs }) +// TODO: rename - properties? + /** * destructure :: Signal S => [String] -> S { k: v } -> [S Any] * From 99f46eb67f4792906aacba9463bb72a228294d84 Mon Sep 17 00:00:00 2001 From: dehmer Date: Wed, 10 Jul 2024 07:08:28 +0200 Subject: [PATCH 45/73] cleanup: make eslint happy. --- src/shared/signal.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/shared/signal.js b/src/shared/signal.js index 5450ca6e..08587f24 100644 --- a/src/shared/signal.js +++ b/src/shared/signal.js @@ -70,7 +70,6 @@ export const circuitBreaker = (input) => { if (count > 10) { console.warn('frequency too high', x) return undefined - } - else return x + } else return x }) } From dd885d4cc0888a6d379438bca1a92c16249e26eb Mon Sep 17 00:00:00 2001 From: dehmer Date: Wed, 10 Jul 2024 07:09:07 +0200 Subject: [PATCH 46/73] cleanup: removed TODOs. --- src/shared/signal.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/shared/signal.js b/src/shared/signal.js index 08587f24..d7ca5177 100644 --- a/src/shared/signal.js +++ b/src/shared/signal.js @@ -1,8 +1,6 @@ import * as R from 'ramda' import Signal from '@syncpoint/signal' -// TODO: move to @syncpoint/signal - /** * select :: Signal S => [a -> Boolean] -> S a -> [S a] * @@ -38,8 +36,6 @@ export const split = R.curry((fns, signal) => { return outputs }) -// TODO: rename - properties? - /** * destructure :: Signal S => [String] -> S { k: v } -> [S Any] * From 63cc7dd8d1e6ffc192956ecc585e01c16aa7f72b Mon Sep 17 00:00:00 2001 From: dehmer Date: Wed, 10 Jul 2024 08:42:37 +0200 Subject: [PATCH 47/73] clean-up. --- src/renderer/ol/style/_evalSync.js | 2 -- src/renderer/ol/style/_rewrite.js | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/renderer/ol/style/_evalSync.js b/src/renderer/ol/style/_evalSync.js index 227ff98a..c1e86fd1 100644 --- a/src/renderer/ol/style/_evalSync.js +++ b/src/renderer/ol/style/_evalSync.js @@ -2,14 +2,12 @@ import { echelonCode } from '../../symbology/2525c' import { echelons } from './echelon' import { Jexl } from 'jexl' - const jexl = new Jexl() /** * */ const evalSync = context => { - const evalSync = textField => Array.isArray(textField) ? textField.map(evalSync).filter(Boolean).join('\n') : jexl.evalSync(textField, context) diff --git a/src/renderer/ol/style/_rewrite.js b/src/renderer/ol/style/_rewrite.js index 838ffc66..372ba5a0 100644 --- a/src/renderer/ol/style/_rewrite.js +++ b/src/renderer/ol/style/_rewrite.js @@ -5,4 +5,4 @@ export default fn => ({ geometry, ...rest }) => geometry ? ({ geometry: fn(geometry), ...rest }) - : rest \ No newline at end of file + : rest From 3d9c79ca182dec9c0530fb50ae9e214d68b2baf3 Mon Sep 17 00:00:00 2001 From: dehmer Date: Wed, 10 Jul 2024 18:20:19 +0200 Subject: [PATCH 48/73] marker styles. --- src/renderer/model/sources/featureSource.js | 8 ++++- src/renderer/ol/style/_evalSync.js | 15 ++++++--- src/renderer/ol/style/corridor.js | 5 ++- src/renderer/ol/style/fallback.js | 11 +------ src/renderer/ol/style/graphics.js | 20 ++++++++++++ src/renderer/ol/style/linestring.js | 5 ++- src/renderer/ol/style/marker.js | 27 ++++++++++++++++ src/renderer/ol/style/multipoint.js | 5 ++- src/renderer/ol/style/polygon.js | 5 ++- src/renderer/ol/style/styles.js | 31 ++++++++----------- src/renderer/ol/style/{point.js => symbol.js} | 18 +++-------- 11 files changed, 92 insertions(+), 58 deletions(-) create mode 100644 src/renderer/ol/style/graphics.js create mode 100644 src/renderer/ol/style/marker.js rename src/renderer/ol/style/{point.js => symbol.js} (61%) diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index f5a90da4..befb0703 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -119,7 +119,13 @@ export const featureSource = services => { ;(async () => { state.styles = await store.dictionary('style+') - const features = (await store.tuples(ID.FEATURE_SCOPE)) + const tuples = [ + ...await store.tuples(ID.FEATURE_SCOPE), + ...await store.tuples(ID.MARKER_SCOPE), + ...await store.tuples(ID.MEASURE_SCOPE) + ] + + const features = tuples .map(([id, value]) => ({ id, ...value })) .map(readFeature(state)) source.addFeatures(features) diff --git a/src/renderer/ol/style/_evalSync.js b/src/renderer/ol/style/_evalSync.js index c1e86fd1..f7460aff 100644 --- a/src/renderer/ol/style/_evalSync.js +++ b/src/renderer/ol/style/_evalSync.js @@ -33,8 +33,15 @@ const evalSync = context => { return replaceAll } -export default (sidc, properties) => { - const sizeCode = echelonCode(sidc) - const echelonText = (sizeCode === '*' || sizeCode === '-') ? '' : echelons[sizeCode]?.text - return evalSync({ modifiers: properties, echelon: echelonText }) +export default (sidc, modifiers) => { + const code = echelonCode(sidc) + const echelon = + (code === '*' || code === '-') + ? '' + : echelons[code]?.text + + return evalSync({ + modifiers, + echelon + }) } diff --git a/src/renderer/ol/style/corridor.js b/src/renderer/ol/style/corridor.js index 8d8b923a..cc1f00ad 100644 --- a/src/renderer/ol/style/corridor.js +++ b/src/renderer/ol/style/corridor.js @@ -1,5 +1,6 @@ import Signal from '@syncpoint/signal' import styles from './corridor-styles/index' +import graphics from './graphics' import _context from './_context' import _shape from './_shape' @@ -9,11 +10,9 @@ export default $ => { $.jtsGeometry = $.geometry.ap($.read) $.context = Signal.link(_context, [$.jtsGeometry, $.resolution]) - // ==> Mandatory slots to derive resulting style: - $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) $.selection = Signal.link(_selection, [$.selectionMode, $.jtsGeometry]) $.labels = Signal.of([]) - // <== Mandatory slots + return graphics($) } diff --git a/src/renderer/ol/style/fallback.js b/src/renderer/ol/style/fallback.js index 4381b880..8a8d6f12 100644 --- a/src/renderer/ol/style/fallback.js +++ b/src/renderer/ol/style/fallback.js @@ -1,13 +1,4 @@ import Signal from '@syncpoint/signal' import defaultStyle from './defaultStyle' -export default $ => { - - // ==> Mandatory slots to derive resulting style: - - $.labels = Signal.of([]) - $.selection = Signal.of([]) - $.shape = Signal.of([defaultStyle()]) - - // <== Mandatory slots -} +export default () => Signal.of([defaultStyle()]) diff --git a/src/renderer/ol/style/graphics.js b/src/renderer/ol/style/graphics.js new file mode 100644 index 00000000..66885167 --- /dev/null +++ b/src/renderer/ol/style/graphics.js @@ -0,0 +1,20 @@ +import * as R from 'ramda' +import Signal from '@syncpoint/signal' + +export default $ => { + + $.styles = Signal.link( + (...styles) => styles.reduce(R.concat), + [ + $.shape, + $.labels, + $.selection + ]) + + return $.styles + .ap($.styleRegistry) + .ap($.evalSync) + .ap($.clip) + .ap($.rewrite) + .ap($.styleFactory) +} diff --git a/src/renderer/ol/style/linestring.js b/src/renderer/ol/style/linestring.js index 074c1ee7..6758fdcd 100644 --- a/src/renderer/ol/style/linestring.js +++ b/src/renderer/ol/style/linestring.js @@ -2,6 +2,7 @@ import Signal from '@syncpoint/signal' import labels from './linestring-styles/labels' import styles from './linestring-styles/index' import placement from './linestring-styles/placement' +import graphics from './graphics' import _smoothenedGeometry from './_smoothenedGeometry' import _simplifiedGeometry from './_simplifiedGeometry' @@ -20,13 +21,11 @@ export default $ => { $.context = Signal.link(_context, [$.jtsSmoothenedGeometry, $.resolution]) $.placement = $.jtsSmoothenedGeometry.map(placement) - // ==> Mandatory slots to derive resulting style: - $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) $.selection = Signal.link(_selection, [$.selectionMode, $.jtsSimplifiedGeometry]) $.labels = $.parameterizedSIDC .map(_labels(labels)) .ap($.placement) - // <== Mandatory slots + return graphics($) } diff --git a/src/renderer/ol/style/marker.js b/src/renderer/ol/style/marker.js new file mode 100644 index 00000000..b83f2ebd --- /dev/null +++ b/src/renderer/ol/style/marker.js @@ -0,0 +1,27 @@ +import { Stroke, Circle, RegularShape, Style } from 'ol/style' + +const crosshair = (color, radius = 30) => { + const stroke = new Stroke({ color, width: 2 }) + const bigCircle = new Circle({ stroke, radius: 30 }) + const smallCircle = new Circle({ stroke, radius: radius / 15 }) + + return [ + new Style({ image: bigCircle }), + new Style({ image: smallCircle }), + ...[0, 1, 2, 3].map(direction => new Style({ + image: new RegularShape({ + stroke, + rotation: direction * Math.PI / 2, + points: 2, + radius: radius / 2, + displacement: [0, 0.8 * radius] + }) + }))] +} + +export default $ => + $.selectionMode.map(mode => + mode === 'default' + ? crosshair('black') + : crosshair('red') +) diff --git a/src/renderer/ol/style/multipoint.js b/src/renderer/ol/style/multipoint.js index 381c70d6..5b0970f6 100644 --- a/src/renderer/ol/style/multipoint.js +++ b/src/renderer/ol/style/multipoint.js @@ -3,6 +3,7 @@ import * as TS from '../ts' import labels from './multipoint-styles/labels' import styles from './multipoint-styles/index' import placement from './polygon-styles/placement' +import graphics from './graphics' import _context from './_context' import _labels from './_labels' @@ -20,13 +21,11 @@ export default $ => { $.context = Signal.link(_context, [$.jtsGeometry, $.resolution]) $.placement = $.jtsGeometry.map(_pointBuffer).map(placement) - // ==> Mandatory slots to derive resulting style: - $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) $.selection = Signal.link(_selection, [$.selectionMode, $.jtsGeometry]) $.labels = $.parameterizedSIDC .map(_labels(labels)) .ap($.placement) - // <== Mandatory slots + return graphics($) } diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js index b1520b94..115e1a03 100644 --- a/src/renderer/ol/style/polygon.js +++ b/src/renderer/ol/style/polygon.js @@ -2,6 +2,7 @@ import Signal from '@syncpoint/signal' import labels from './polygon-styles/labels' import styles from './polygon-styles/index' import placement from './polygon-styles/placement' +import graphics from './graphics' import _smoothenedGeometry from './_smoothenedGeometry' import _simplifiedGeometry from './_simplifiedGeometry' @@ -20,13 +21,11 @@ export default $ => { $.context = Signal.link(_context, [$.jtsSmoothenedGeometry, $.resolution]) $.placement = $.jtsSmoothenedGeometry.map(placement) - // ==> Mandatory slots to derive resulting style: - $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) $.selection = Signal.link(_selection, [$.selectionMode, $.jtsSimplifiedGeometry]) $.labels = $.parameterizedSIDC .map(_labels(labels)) .ap($.placement) - // <== Mandatory slots + return graphics($) } diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index b3f28731..e47f0ae8 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -3,14 +3,16 @@ import { parameterized } from '../../symbology/2525c' import Signal from '@syncpoint/signal' import * as Geometry from '../../model/geometry' import styleRegistry from './styleRegistry' -import point from './point' +import symbol from './symbol' import polygon from './polygon' import linestring from './linestring' import multipoint from './multipoint' import corridor from './corridor' +import marker from './marker' import fallback from './fallback' import transform from './_transform' import { styleFactory } from './styleFactory' +import * as ID from '../../ids' import _colorScheme from './_colorScheme' import _schemeStyle from './_schemeStyle' @@ -24,13 +26,13 @@ export default feature => { $.sidc = $.properties.map(R.prop('sidc')) $.parameterizedSIDC = $.sidc.map(parameterized) - $.geometryType = $.geometry.map(Geometry.geometryType) const [read, write, pointResolution] = transform($.geometry) $.read = read $.rewrite = write.map(fn => xs => xs.map(_rewrite(fn))) $.pointResolution = pointResolution $.resolution = $.centerResolution.ap($.pointResolution) + $.clip = $.resolution.map(_clip) $.evalSync = Signal.link(_evalSync, [$.sidc, $.properties]) $.colorScheme = Signal.link(_colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) @@ -41,22 +43,15 @@ export default feature => { .map(fn => xs => xs.map(fn)) $.styleFactory = Signal.of(xs => xs.flatMap(styleFactory)) + const featureId = feature.getId() const geometryType = Geometry.geometryType(feature.getGeometry()) - if (geometryType === 'Point') point($) - else if (geometryType === 'Polygon') polygon($) - else if (geometryType === 'LineString') linestring($) - else if (geometryType === 'MultiPoint') multipoint($) - else if (geometryType === 'LineString:Point') corridor($) - else fallback($) - - // Primary shape style must go first because of clipping: - $.styles = Signal.link((...styles) => styles.reduce(R.concat), [$.shape, $.labels, $.selection]) - $.clip = $.resolution.map(_clip) - return $.styles - .ap($.styleRegistry) - .ap($.evalSync) - .ap($.clip) - .ap($.rewrite) - .ap($.styleFactory) + if (ID.isMarkerId(featureId)) return marker($) + // else if (ID.isMeasureId(featureId)) console.log('MEASURE') + else if (geometryType === 'Point') return symbol($) + else if (geometryType === 'Polygon') return polygon($) + else if (geometryType === 'LineString') return linestring($) + else if (geometryType === 'MultiPoint') return multipoint($) + else if (geometryType === 'LineString:Point') return corridor($) + else return fallback($) } diff --git a/src/renderer/ol/style/point.js b/src/renderer/ol/style/symbol.js similarity index 61% rename from src/renderer/ol/style/point.js rename to src/renderer/ol/style/symbol.js index 7bdaec2d..df0fbc19 100644 --- a/src/renderer/ol/style/point.js +++ b/src/renderer/ol/style/symbol.js @@ -6,19 +6,8 @@ import { MODIFIERS } from '../../symbology/2525c' * */ export default $ => { - - // Point also need to contribute simplified JTS/UTM geometry, - // because it is used to derive selection styles. - // - $.jtsSimplifiedGeometry = $.geometry.ap($.read) - - // ==> Mandatory slots to derive resulting style: - - $.labels = Signal.of([]) - $.selection = Signal.of([]) $.shape = Signal.link((properties, geometry) => { const sidc = properties.sidc - const modifiers = Object.entries(properties) .filter(([key, value]) => MODIFIERS[key] && value) .reduce((acc, [key, value]) => R.tap(acc => (acc[MODIFIERS[key]] = value), acc), {}) @@ -29,7 +18,10 @@ export default $ => { 'symbol-code': sidc, 'symbol-modifiers': modifiers }] - }, [$.properties, $.jtsSimplifiedGeometry]) - // <== Mandatory slots + }, [$.properties, $.geometry]) + + return $.shape + .ap($.styleRegistry) + .ap($.styleFactory) } From b21e79056a6270fa836756caceffccbd9d57aad0 Mon Sep 17 00:00:00 2001 From: dehmer Date: Fri, 12 Jul 2024 11:54:34 +0200 Subject: [PATCH 49/73] source: add feature on put event. --- src/renderer/model/sources/featureSource.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index f5a90da4..2f0186ea 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -150,9 +150,13 @@ export const featureSource = services => { }) feature.on(({ type, key, value }) => { - const feature = source.getFeatureById(key) + let feature = source.getFeatureById(key) if (type === 'del') source.removeFeature(feature) - else feature.$.properties(value.properties) + else if (feature) feature.$.properties(value.properties) + else { + feature = readFeature(state, { id: key, ...value }) + source.addFeature(feature) + } }) // <== batch event handling From a380591b5a774576e4093770f8bd1782972b7b70 Mon Sep 17 00:00:00 2001 From: dehmer Date: Fri, 12 Jul 2024 11:54:55 +0200 Subject: [PATCH 50/73] deps: updated @syncpoint/signs --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index c5b41cca..e6bbe005 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.0", "@syncpoint/signal": "github:syncpoint/signal#vite", - "@syncpoint/signs": "github:syncpoint/signs#vite", + "@syncpoint/signs": "github:syncpoint/signs", "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", "color": "^4.2.3", @@ -2941,7 +2941,7 @@ }, "node_modules/@syncpoint/signs": { "version": "1.1.0", - "resolved": "git+ssh://git@github.com/syncpoint/signs.git#c1b8e45b75d3c88e9f2a19a294e7da4834bcd2d6", + "resolved": "git+ssh://git@github.com/syncpoint/signs.git#8449525ee57f75f6705e923f867051656f5e65a7", "dependencies": { "ramda": "^0.30.1", "svg-path-bbox": "^2.0.0" diff --git a/package.json b/package.json index 474f0d4f..b0cf6481 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.0", "@syncpoint/signal": "github:syncpoint/signal#vite", - "@syncpoint/signs": "github:syncpoint/signs#vite", + "@syncpoint/signs": "github:syncpoint/signs", "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", "color": "^4.2.3", From 9c40e280922f90303a8da33b583acb221c890a81 Mon Sep 17 00:00:00 2001 From: dehmer Date: Fri, 12 Jul 2024 15:35:59 +0200 Subject: [PATCH 51/73] shuffled some signals around. --- src/renderer/ol/style/corridor.js | 7 +++---- src/renderer/ol/style/graphics.js | 19 +++++++++++++++++-- src/renderer/ol/style/linestring.js | 4 +++- src/renderer/ol/style/multipoint.js | 4 +++- src/renderer/ol/style/polygon.js | 6 +++--- src/renderer/ol/style/styles.js | 13 ------------- 6 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/renderer/ol/style/corridor.js b/src/renderer/ol/style/corridor.js index cc1f00ad..a8e446ec 100644 --- a/src/renderer/ol/style/corridor.js +++ b/src/renderer/ol/style/corridor.js @@ -6,13 +6,12 @@ import _context from './_context' import _shape from './_shape' import _selection from './_selection' -export default $ => { +const specifics = $ => { $.jtsGeometry = $.geometry.ap($.read) $.context = Signal.link(_context, [$.jtsGeometry, $.resolution]) - $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) $.selection = Signal.link(_selection, [$.selectionMode, $.jtsGeometry]) $.labels = Signal.of([]) - - return graphics($) } + +export default graphics(specifics) diff --git a/src/renderer/ol/style/graphics.js b/src/renderer/ol/style/graphics.js index 66885167..d89d54d6 100644 --- a/src/renderer/ol/style/graphics.js +++ b/src/renderer/ol/style/graphics.js @@ -1,7 +1,21 @@ import * as R from 'ramda' import Signal from '@syncpoint/signal' +import transform from './_transform' -export default $ => { +import _rewrite from './_rewrite' +import _evalSync from './_evalSync' +import _clip from './_clip' + +export default specifics => $ => { + const [read, write, pointResolution] = transform($.geometry) + $.read = read + $.rewrite = write.map(fn => xs => xs.map(_rewrite(fn))) + $.pointResolution = pointResolution + $.resolution = $.centerResolution.ap($.pointResolution) + $.clip = $.resolution.map(_clip) + $.evalSync = Signal.link(_evalSync, [$.sidc, $.properties]) + + specifics($) $.styles = Signal.link( (...styles) => styles.reduce(R.concat), @@ -9,7 +23,8 @@ export default $ => { $.shape, $.labels, $.selection - ]) + ] + ) return $.styles .ap($.styleRegistry) diff --git a/src/renderer/ol/style/linestring.js b/src/renderer/ol/style/linestring.js index 6758fdcd..20d661fa 100644 --- a/src/renderer/ol/style/linestring.js +++ b/src/renderer/ol/style/linestring.js @@ -12,7 +12,7 @@ import _shape from './_shape' import _lineSmoothing from './_lineSmoothing' import _selection from './_selection' -export default $ => { +const specifics = $ => { $.simplifiedGeometry = Signal.link(_simplifiedGeometry, [$.geometry, $.centerResolution]) $.jtsSimplifiedGeometry = $.simplifiedGeometry.ap($.read) $.lineSmoothing = $.effectiveStyle.map(_lineSmoothing) @@ -29,3 +29,5 @@ export default $ => { return graphics($) } + +export default graphics(specifics) diff --git a/src/renderer/ol/style/multipoint.js b/src/renderer/ol/style/multipoint.js index 5b0970f6..ce0131e3 100644 --- a/src/renderer/ol/style/multipoint.js +++ b/src/renderer/ol/style/multipoint.js @@ -16,7 +16,7 @@ const _pointBuffer = geometry => { return TS.pointBuffer(TS.point(C))(segment.getLength()) } -export default $ => { +const specifics = $ => { $.jtsGeometry = $.geometry.ap($.read) $.context = Signal.link(_context, [$.jtsGeometry, $.resolution]) $.placement = $.jtsGeometry.map(_pointBuffer).map(placement) @@ -29,3 +29,5 @@ export default $ => { return graphics($) } + +export default graphics(specifics) diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js index 115e1a03..c32af3fe 100644 --- a/src/renderer/ol/style/polygon.js +++ b/src/renderer/ol/style/polygon.js @@ -12,7 +12,7 @@ import _shape from './_shape' import _lineSmoothing from './_lineSmoothing' import _selection from './_selection' -export default $ => { +const specifics = $ => { $.simplifiedGeometry = Signal.link(_simplifiedGeometry, [$.geometry, $.centerResolution]) $.jtsSimplifiedGeometry = $.simplifiedGeometry.ap($.read) $.lineSmoothing = $.effectiveStyle.map(_lineSmoothing) @@ -26,6 +26,6 @@ export default $ => { $.labels = $.parameterizedSIDC .map(_labels(labels)) .ap($.placement) - - return graphics($) } + +export default graphics(specifics) diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index e47f0ae8..a0b344e9 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -10,31 +10,18 @@ import multipoint from './multipoint' import corridor from './corridor' import marker from './marker' import fallback from './fallback' -import transform from './_transform' import { styleFactory } from './styleFactory' import * as ID from '../../ids' import _colorScheme from './_colorScheme' import _schemeStyle from './_schemeStyle' import _effectiveStyle from './_effectiveStyle' -import _rewrite from './_rewrite' -import _evalSync from './_evalSync' -import _clip from './_clip' export default feature => { const { $ } = feature $.sidc = $.properties.map(R.prop('sidc')) $.parameterizedSIDC = $.sidc.map(parameterized) - - const [read, write, pointResolution] = transform($.geometry) - $.read = read - $.rewrite = write.map(fn => xs => xs.map(_rewrite(fn))) - $.pointResolution = pointResolution - $.resolution = $.centerResolution.ap($.pointResolution) - $.clip = $.resolution.map(_clip) - - $.evalSync = Signal.link(_evalSync, [$.sidc, $.properties]) $.colorScheme = Signal.link(_colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) $.schemeStyle = Signal.link(_schemeStyle, [$.sidc, $.colorScheme]) $.effectiveStyle = Signal.link(_effectiveStyle, [$.globalStyle, $.schemeStyle, $.layerStyle, $.featureStyle]) From e939407f4412269e5a22c53ff58c4ca4dd49aef6 Mon Sep 17 00:00:00 2001 From: dehmer Date: Sat, 13 Jul 2024 08:58:43 +0200 Subject: [PATCH 52/73] support measurement styles. --- .../interaction/{measure => }/GeometryType.js | 0 .../ol/interaction/draw-interaction.js | 13 +- .../ol/interaction/measure/LineStringStyle.js | 80 +++++++ .../ol/interaction/measure/PolygonStyle.js | 54 +++++ .../ol/interaction/measure/baseStyle.js | 26 +++ src/renderer/ol/interaction/measure/index.js | 7 +- src/renderer/ol/interaction/measure/style.js | 198 ++---------------- src/renderer/ol/interaction/measure/tools.js | 2 +- src/renderer/ol/style/measure.js | 19 ++ src/renderer/ol/style/styles.js | 4 +- 10 files changed, 202 insertions(+), 201 deletions(-) rename src/renderer/ol/interaction/{measure => }/GeometryType.js (100%) create mode 100644 src/renderer/ol/interaction/measure/LineStringStyle.js create mode 100644 src/renderer/ol/interaction/measure/PolygonStyle.js create mode 100644 src/renderer/ol/interaction/measure/baseStyle.js create mode 100644 src/renderer/ol/style/measure.js diff --git a/src/renderer/ol/interaction/measure/GeometryType.js b/src/renderer/ol/interaction/GeometryType.js similarity index 100% rename from src/renderer/ol/interaction/measure/GeometryType.js rename to src/renderer/ol/interaction/GeometryType.js diff --git a/src/renderer/ol/interaction/draw-interaction.js b/src/renderer/ol/interaction/draw-interaction.js index 1a60934a..552327dd 100644 --- a/src/renderer/ol/interaction/draw-interaction.js +++ b/src/renderer/ol/interaction/draw-interaction.js @@ -6,18 +6,7 @@ import { writeFeatureObject } from '../../store/FeatureStore' import * as TS from '../ts' import * as EPSG from '../../epsg' import { PI_OVER_2, PI_OVER_4, SQRT_2 } from '../../../shared/Math' - -const GeometryType = { - POINT: 'Point', - LINE_STRING: 'LineString', - LINEAR_RING: 'LinearRing', - POLYGON: 'Polygon', - MULTI_POINT: 'MultiPoint', - MULTI_LINE_STRING: 'MultiLineString', - MULTI_POLYGON: 'MultiPolygon', - GEOMETRY_COLLECTION: 'GeometryCollection', - CIRCLE: 'Circle' -} +import GeometryType from './GeometryType' export default options => { const { services, map } = options diff --git a/src/renderer/ol/interaction/measure/LineStringStyle.js b/src/renderer/ol/interaction/measure/LineStringStyle.js new file mode 100644 index 00000000..d865ab01 --- /dev/null +++ b/src/renderer/ol/interaction/measure/LineStringStyle.js @@ -0,0 +1,80 @@ +import * as geom from 'ol/geom' +import { Circle, Fill, Stroke, Style, Text } from 'ol/style' +import { FONT } from './baseStyle' +import { angle, radiansAngle, length, getLastSegmentCoordinates } from './tools' + +export const LineString = geometry => { + const styles = [] + let numberOfSegments = 0 + + geometry.forEachSegment((start, end) => { + const segment = new geom.LineString([start, end]) + numberOfSegments++ + styles.push(new Style({ + geometry: segment, + text: new Text({ + text: `${length(segment)}\n\n${angle(segment)}`, + font: FONT, + fill: new Fill({ + color: 'black' + }), + stroke: new Stroke({ + color: 'white', + width: 5 + }), + placement: 'line', + overflow: true, + textBaseline: 'middle' + }) + })) + }) + + /* first point of the linestring */ + styles.push(new Style({ + geometry: new geom.Point(geometry.getFirstCoordinate()), + image: new Circle({ + radius: 6, + fill: new Fill({ + color: 'green' + }) + }) + })) + + const lastSegment = new geom.LineString(getLastSegmentCoordinates(geometry)) + const alpha = radiansAngle(lastSegment) + + /* set style and label for last point but only if we have more than one segment */ + + styles.push( + new Style({ + geometry: new geom.Point(geometry.getLastCoordinate()), + image: new Circle({ + radius: 6, + fill: new Fill({ + color: 'red' + }) + }), + text: (numberOfSegments === 1) + ? '' + : new Text({ + text: length(geometry), + font: FONT, + fill: new Fill({ + color: 'black' + }), + stroke: new Stroke({ + color: 'white', + width: 5 + }), + offsetX: 25 * Math.cos(alpha), + offsetY: -25 * Math.sin(alpha), + placement: 'point', + textAlign: Math.abs(alpha) < Math.PI / 2 ? 'left' : 'right', + overflow: true, + textBaseline: 'ideographic' + }) + }) + ) + + return styles +} diff --git a/src/renderer/ol/interaction/measure/PolygonStyle.js b/src/renderer/ol/interaction/measure/PolygonStyle.js new file mode 100644 index 00000000..1a4469c0 --- /dev/null +++ b/src/renderer/ol/interaction/measure/PolygonStyle.js @@ -0,0 +1,54 @@ +import * as geom from 'ol/geom' +import { Fill, Stroke, Style, Text } from 'ol/style' +import { FONT } from './baseStyle' +import { length, area } from './tools' + +export const Polygon = geometry => { + + const styles = [] + const coordinates = geometry.getCoordinates()[0] + const numberOfSegments = coordinates.length - 1 + + for (let i = 0; i < numberOfSegments; i++) { + const segment = new geom.LineString([coordinates[i], coordinates[i + 1]]) + styles.push(new Style({ + geometry: segment, + text: new Text({ + text: `${length(segment)}\n\n`, + font: FONT, + fill: new Fill({ + color: 'black' + }), + stroke: new Stroke({ + color: 'white', + width: 5 + }), + placement: 'line', + overflow: true, + textBaseline: 'middle' + }) + })) + } + + styles.push( + new Style({ + geometry: geometry.getInteriorPoint(), + text: new Text({ + text: `${area(geometry)}\n${length(geometry)}`, + font: FONT, + fill: new Fill({ + color: 'black' + }), + stroke: new Stroke({ + color: 'white', + width: 5 + }), + placement: 'point', + overflow: true, + textBaseline: 'ideographic' + }) + }) + ) + + return styles +} diff --git a/src/renderer/ol/interaction/measure/baseStyle.js b/src/renderer/ol/interaction/measure/baseStyle.js new file mode 100644 index 00000000..7f6d4dff --- /dev/null +++ b/src/renderer/ol/interaction/measure/baseStyle.js @@ -0,0 +1,26 @@ +import { Circle, Fill, Stroke, Style } from 'ol/style' +export const FONT = '12px sans-serif' + +export const baseStyle = selected => [ + new Style({ + stroke: new Stroke({ + color: selected ? 'blue' : 'red', + width: 4 + }) + }), + new Style({ + stroke: new Stroke({ + color: 'white', + lineDash: [15, 15], + width: 4 + }) + }), + new Style({ + image: new Circle({ + radius: 4, + fill: new Fill({ + color: 'blue' + }) + }) + }) +] diff --git a/src/renderer/ol/interaction/measure/index.js b/src/renderer/ol/interaction/measure/index.js index 59e633cd..6f643fb2 100644 --- a/src/renderer/ol/interaction/measure/index.js +++ b/src/renderer/ol/interaction/measure/index.js @@ -5,8 +5,9 @@ import { Vector as VectorSource } from 'ol/source' import { Vector as VectorLayer } from 'ol/layer' import Circle from 'ol/geom/Circle' import uuid from '../../../../shared/uuid' -import GeometryType from './GeometryType' -import { baseStyle, stylefunctionForGeometryType } from './style' +import GeometryType from '../GeometryType' +import { baseStyle } from './baseStyle' +import { styleFN } from './style' import { getLastSegmentCoordinates } from './tools' import { militaryFormat } from '../../../../shared/datetime' import * as ID from '../../../ids' @@ -55,7 +56,7 @@ export default ({ map, services }) => { }) drawInteraction.once('drawstart', ({ feature }) => { - feature.setStyle(stylefunctionForGeometryType(geometryType, () => true)) + feature.setStyle(styleFN(() => true)) if (geometryType !== GeometryType.LINE_STRING) return /* circle helper is only supported when measuring distances */ diff --git a/src/renderer/ol/interaction/measure/style.js b/src/renderer/ol/interaction/measure/style.js index c33aa2d4..f0d2f94f 100644 --- a/src/renderer/ol/interaction/measure/style.js +++ b/src/renderer/ol/interaction/measure/style.js @@ -1,189 +1,19 @@ -import { Circle as CircleStyle, Fill, Stroke, Style, Text as TextStyle } from 'ol/style' -import Point from 'ol/geom/Point' -import LineString from 'ol/geom/LineString' -import GeometryType from './GeometryType' +import { Polygon } from './PolygonStyle' +import { LineString } from './LineStringStyle' +import { baseStyle } from './baseStyle' -import { angle, radiansAngle, length, getLastSegmentCoordinates, area } from './tools' - -const FONT = '12px sans-serif' - -export const baseStyle = isSelected => [ - new Style({ - stroke: new Stroke({ - color: isSelected ? 'blue' : 'red', - width: 4 - }) - }), - new Style({ - stroke: new Stroke({ - color: 'white', - lineDash: [15, 15], - width: 4 - }) - }), - new Style({ - image: new CircleStyle({ - radius: 4, - fill: new Fill({ - color: 'blue' - }) - }) - }) -] - -/* style function for POLYGON */ -const polygonStyle = feature => { - - const styles = [] - const geometry = feature.getGeometry() - - const coordinates = geometry.getCoordinates()[0] - const numberOfSegments = coordinates.length - 1 - - for (let i = 0; i < numberOfSegments; i++) { - const segment = new LineString([coordinates[i], coordinates[i + 1]]) - styles.push(new Style({ - geometry: segment, - text: new TextStyle({ - text: `${length(segment)}\n\n`, - font: FONT, - fill: new Fill({ - color: 'black' - }), - stroke: new Stroke({ - color: 'white', - width: 5 - }), - placement: 'line', - overflow: true, - textBaseline: 'middle' - }) - })) - } - - styles.push( - new Style({ - geometry: geometry.getInteriorPoint(), - text: new TextStyle({ - text: `${area(geometry)}\n${length(geometry)}`, - font: FONT, - fill: new Fill({ - color: 'black' - }), - stroke: new Stroke({ - color: 'white', - width: 5 - }), - placement: 'point', - overflow: true, - textBaseline: 'ideographic' - }) - }) - ) - - return styles -} - -/* style function for LINE_STRING */ -const linestringStyle = feature => { - const styles = [] - const geometry = feature.getGeometry() - - let numberOfSegments = 0 - - geometry.forEachSegment((start, end) => { - const segment = new LineString([start, end]) - numberOfSegments++ - styles.push(new Style({ - geometry: segment, - text: new TextStyle({ - text: `${length(segment)}\n\n${angle(segment)}`, - font: FONT, - fill: new Fill({ - color: 'black' - }), - stroke: new Stroke({ - color: 'white', - width: 5 - }), - placement: 'line', - overflow: true, - textBaseline: 'middle' - }) - })) - }) - - /* first point of the linestring */ - styles.push(new Style({ - geometry: new Point(geometry.getFirstCoordinate()), - image: new CircleStyle({ - radius: 6, - fill: new Fill({ - color: 'green' - }) - }) - })) - - const lastSegment = new LineString(getLastSegmentCoordinates(geometry)) - const alpha = radiansAngle(lastSegment) - - /* set style and label for last point but only if we have more than one segment */ - - styles.push( - new Style({ - geometry: new Point(geometry.getLastCoordinate()), - image: new CircleStyle({ - radius: 6, - fill: new Fill({ - color: 'red' - }) - }), - text: (numberOfSegments === 1) - ? '' - : new TextStyle({ - text: length(geometry), - font: FONT, - fill: new Fill({ - color: 'black' - }), - stroke: new Stroke({ - color: 'white', - width: 5 - }), - offsetX: 25 * Math.cos(alpha), - offsetY: -25 * Math.sin(alpha), - placement: 'point', - textAlign: Math.abs(alpha) < Math.PI / 2 ? 'left' : 'right', - overflow: true, - textBaseline: 'ideographic' - }) - }) - ) - - - return styles -} - -/*** - * - * isSelected: function (feature) => Boolean - */ -export const stylist = (isSelected) => (feature) => { - const geometry = feature.getGeometry() - return stylefunctionForGeometryType(geometry.getType(), isSelected)(feature) +export const STYLES = { + Polygon, + LineString } -/* returns a style function for the given geometry type and selection state */ -export const stylefunctionForGeometryType = (geometryType, isSelected) => { - // const styles = - - if (geometryType === GeometryType.POLYGON) { - return feature => [...(baseStyle(isSelected(feature))), ...polygonStyle(feature)] - } else if (geometryType === GeometryType.LINE_STRING) { - return feature => [...(baseStyle(isSelected(feature))), ...linestringStyle(feature)] - } - - return () => { - baseStyle(() => false) +export const styleFN = (isSelected) => { + return feature => { + const geometry = feature.getGeometry() + const geometryType = geometry.getType() + return [ + ...baseStyle(isSelected(feature)), + ...STYLES[geometryType](geometry) + ] } } diff --git a/src/renderer/ol/interaction/measure/tools.js b/src/renderer/ol/interaction/measure/tools.js index 09485a86..98721ecd 100644 --- a/src/renderer/ol/interaction/measure/tools.js +++ b/src/renderer/ol/interaction/measure/tools.js @@ -1,4 +1,4 @@ -import GeometryType from './GeometryType' +import GeometryType from '../GeometryType' import { getArea, getLength } from 'ol/sphere' const meterFormatter = new Intl.NumberFormat(window.navigator.userLanguage || window.navigator.language, { maximumFractionDigits: 1, style: 'unit', unit: 'meter' }) diff --git a/src/renderer/ol/style/measure.js b/src/renderer/ol/style/measure.js new file mode 100644 index 00000000..dd7a6f6c --- /dev/null +++ b/src/renderer/ol/style/measure.js @@ -0,0 +1,19 @@ +import * as R from 'ramda' +import Signal from '@syncpoint/signal' +import { STYLES } from '../interaction/measure/style' +import { baseStyle } from '../interaction/measure/baseStyle' + +export default $ => { + $.selected = $.selectionMode.map(mode => mode !== 'default') + $.baseStyle = $.selected.map(baseStyle) + $.styleFN = $.geometryType.map(type => STYLES[type]) + $.geometryStyle = $.geometry.ap($.styleFN) + + return Signal.link( + (...styles) => styles.reduce(R.concat), + [ + $.baseStyle, + $.geometryStyle + ] + ) +} diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index a0b344e9..9ecfd048 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -9,6 +9,7 @@ import linestring from './linestring' import multipoint from './multipoint' import corridor from './corridor' import marker from './marker' +import measure from './measure' import fallback from './fallback' import { styleFactory } from './styleFactory' import * as ID from '../../ids' @@ -20,6 +21,7 @@ import _effectiveStyle from './_effectiveStyle' export default feature => { const { $ } = feature + $.geometryType = $.geometry.map(geometry => geometry.getType()) $.sidc = $.properties.map(R.prop('sidc')) $.parameterizedSIDC = $.sidc.map(parameterized) $.colorScheme = Signal.link(_colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) @@ -34,7 +36,7 @@ export default feature => { const geometryType = Geometry.geometryType(feature.getGeometry()) if (ID.isMarkerId(featureId)) return marker($) - // else if (ID.isMeasureId(featureId)) console.log('MEASURE') + else if (ID.isMeasureId(featureId)) return measure($) else if (geometryType === 'Point') return symbol($) else if (geometryType === 'Polygon') return polygon($) else if (geometryType === 'LineString') return linestring($) From 61300dc11ca6a4a2c4a710432ae7f884a7aa346e Mon Sep 17 00:00:00 2001 From: dehmer Date: Sat, 13 Jul 2024 11:10:05 +0200 Subject: [PATCH 53/73] symbol: multi-select style. --- src/renderer/model/sources/featureSource.js | 12 ++++++------ src/renderer/ol/style/symbol.js | 20 ++++++++++++++++---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index a89ac250..429799ee 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -117,6 +117,8 @@ export const featureSource = services => { } }) + const getFeatureById = source.getFeatureById.bind(source) + ;(async () => { state.styles = await store.dictionary('style+') const tuples = [ @@ -150,13 +152,13 @@ export const featureSource = services => { featureStyle.on(({ type, key, value }) => { const featureId = ID.featureId(key) - const feature = source.getFeatureById(featureId) + const feature = getFeatureById(featureId) if (type === 'del') delete state.styles[key] if (feature) feature.$.featureStyle(type === 'put' ? value : {}) }) feature.on(({ type, key, value }) => { - let feature = source.getFeatureById(key) + let feature = getFeatureById(key) if (type === 'del') source.removeFeature(feature) else if (feature) feature.$.properties(value.properties) else { @@ -173,13 +175,11 @@ export const featureSource = services => { : 'singleselect' const apply = mode => feature => { - // FIXME: know sure why this may happen, but it does. - if (!feature || !feature.$) return feature.$.selectionMode(mode) } - deselected.map(source.getFeatureById.bind(source)).map(apply('default')) - selection.selected().map(source.getFeatureById.bind(source)).map(apply(mode)) + deselected.map(getFeatureById).map(apply('default')) + selection.selected().map(getFeatureById).map(apply(mode)) }) return source diff --git a/src/renderer/ol/style/symbol.js b/src/renderer/ol/style/symbol.js index df0fbc19..d8ac5fa2 100644 --- a/src/renderer/ol/style/symbol.js +++ b/src/renderer/ol/style/symbol.js @@ -6,7 +6,7 @@ import { MODIFIERS } from '../../symbology/2525c' * */ export default $ => { - $.shape = Signal.link((properties, geometry) => { + $.shape = $.properties.map(properties => { const sidc = properties.sidc const modifiers = Object.entries(properties) .filter(([key, value]) => MODIFIERS[key] && value) @@ -14,14 +14,26 @@ export default $ => { return [{ id: 'style:2525c/symbol', - geometry, 'symbol-code': sidc, 'symbol-modifiers': modifiers }] + }, []) - }, [$.properties, $.geometry]) + $.selection = $.selectionMode.map(mode => + mode === 'multiselect' + ? [{ id: 'style:rectangle-handle' }] + : [] + ) - return $.shape + $.styles = Signal.link( + (...styles) => styles.reduce(R.concat), + [ + $.shape, + $.selection + ] + ) + + return $.styles .ap($.styleRegistry) .ap($.styleFactory) } From eda4aa2b2d0d93cc2b1740d85bda75b06125ce9f Mon Sep 17 00:00:00 2001 From: dehmer Date: Sat, 13 Jul 2024 13:49:33 +0200 Subject: [PATCH 54/73] clean-up: FeatureStore/Format. --- src/renderer/components/Project-services.js | 3 +- .../components/properties/Attitude.js | 2 +- .../components/properties/CorridorWidth.js | 2 +- .../properties/GraphicsProperties.js | 2 +- src/renderer/components/properties/Length.js | 2 +- src/renderer/components/properties/Radius.js | 2 +- .../components/properties/RectangleWidth.js | 2 +- src/renderer/model/sources/featureSource.js | 18 ++++--- src/renderer/ol/format.js | 19 +++++++ .../ol/interaction/clone-interaction.js | 2 +- .../ol/interaction/draw-interaction.js | 2 +- src/renderer/ol/interaction/measure/index.js | 2 +- .../ol/interaction/modify-interaction.js | 2 +- .../ol/interaction/translate-interaction.js | 2 +- src/renderer/store/FeatureStore.js | 51 +------------------ src/renderer/store/Store.js | 2 +- src/renderer/store/options/place.js | 2 +- 17 files changed, 45 insertions(+), 72 deletions(-) create mode 100644 src/renderer/ol/format.js diff --git a/src/renderer/components/Project-services.js b/src/renderer/components/Project-services.js index b7a4e45c..066ae0e4 100644 --- a/src/renderer/components/Project-services.js +++ b/src/renderer/components/Project-services.js @@ -62,7 +62,7 @@ export default async projectUUID => { const coordinatesFormat = new CoordinatesFormat(emitter, preferencesStore) const optionStore = new OptionStore(coordinatesFormat, store, sessionStore) const nominatim = new Nominatim(store) - const featureStore = new FeatureStore(store, selection, emitter) + const featureStore = new FeatureStore(store) const searchIndex = new SearchIndex(jsonDB, documentStore, optionStore, emitter, nominatim, sessionStore, spatialIndex) // Key bindings. @@ -128,7 +128,6 @@ export default async projectUUID => { await schema.bootstrap() await tileLayerStore.bootstrap() await searchIndex.bootstrap() - await featureStore.bootstrap() await spatialIndex.bootstrap() return services diff --git a/src/renderer/components/properties/Attitude.js b/src/renderer/components/properties/Attitude.js index 92791c4e..91b0d88d 100644 --- a/src/renderer/components/properties/Attitude.js +++ b/src/renderer/components/properties/Attitude.js @@ -1,7 +1,7 @@ /* eslint-disable react/prop-types */ import React from 'react' import textProperty from './textProperty' -import { readGeometry, writeGeometryObject } from '../../store/FeatureStore' +import { readGeometry, writeGeometryObject } from '../../ol/format' import * as geom from './geometries' const TextProperty = textProperty({ diff --git a/src/renderer/components/properties/CorridorWidth.js b/src/renderer/components/properties/CorridorWidth.js index b87cc79e..cc366688 100644 --- a/src/renderer/components/properties/CorridorWidth.js +++ b/src/renderer/components/properties/CorridorWidth.js @@ -1,7 +1,7 @@ /* eslint-disable react/prop-types */ import React from 'react' import textProperty from './textProperty' -import { readGeometry, writeGeometryObject } from '../../store/FeatureStore' +import { readGeometry, writeGeometryObject } from '../../ol/format' import * as geom from './geometries' const TextProperty = textProperty({ diff --git a/src/renderer/components/properties/GraphicsProperties.js b/src/renderer/components/properties/GraphicsProperties.js index c30e3330..cccb884a 100644 --- a/src/renderer/components/properties/GraphicsProperties.js +++ b/src/renderer/components/properties/GraphicsProperties.js @@ -19,7 +19,7 @@ import GridCols2 from './GridCols2' import CorridorWidth from './CorridorWidth' import * as MILSTD from '../../symbology/2525c' import * as GEOM from '../../model/geometry' -import { readGeometry } from '../../store/FeatureStore' +import { readGeometry } from '../../ol/format' import KProperty from './KProperty' export default props => { diff --git a/src/renderer/components/properties/Length.js b/src/renderer/components/properties/Length.js index 2ee06aaa..7cad2e52 100644 --- a/src/renderer/components/properties/Length.js +++ b/src/renderer/components/properties/Length.js @@ -1,7 +1,7 @@ /* eslint-disable react/prop-types */ import React from 'react' import textProperty from './textProperty' -import { readGeometry, writeGeometryObject } from '../../store/FeatureStore' +import { readGeometry, writeGeometryObject } from '../../ol/format' import * as geom from './geometries' const TextProperty = textProperty({ diff --git a/src/renderer/components/properties/Radius.js b/src/renderer/components/properties/Radius.js index 74cac609..d1e8cac7 100644 --- a/src/renderer/components/properties/Radius.js +++ b/src/renderer/components/properties/Radius.js @@ -1,7 +1,7 @@ /* eslint-disable react/prop-types */ import React from 'react' import textProperty from './textProperty' -import { readGeometry, writeGeometryObject } from '../../store/FeatureStore' +import { readGeometry, writeGeometryObject } from '../../ol/format' import * as geom from './geometries' const TextProperty = textProperty({ diff --git a/src/renderer/components/properties/RectangleWidth.js b/src/renderer/components/properties/RectangleWidth.js index 7ac873b7..393812b3 100644 --- a/src/renderer/components/properties/RectangleWidth.js +++ b/src/renderer/components/properties/RectangleWidth.js @@ -1,7 +1,7 @@ /* eslint-disable react/prop-types */ import React from 'react' import textProperty from './textProperty' -import { readGeometry, writeGeometryObject } from '../../store/FeatureStore' +import { readGeometry, writeGeometryObject } from '../../ol/format' import * as geom from './geometries' const TextProperty = textProperty({ diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index 429799ee..6b4d8356 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -1,19 +1,14 @@ import * as R from 'ramda' import Signal from '@syncpoint/signal' import VectorSource from 'ol/source/Vector' -import GeoJSON from 'ol/format/GeoJSON' import * as ID from '../../ids' import styles from '../../ol/style/styles' +import { format } from '../../ol/format' import { flat, select } from '../../../shared/signal' import { setCoordinates } from '../geometry' import keyequals from '../../ol/style/keyequals' import isEqual from 'react-fast-compare' -const format = new GeoJSON({ - dataProjection: 'EPSG:3857', - featureProjection: 'EPSG:3857' -}) - /** * Read features from GeoJSON to ol/Feature and * create input signals for style calculation. @@ -175,11 +170,18 @@ export const featureSource = services => { : 'singleselect' const apply = mode => feature => { + console.log(feature, feature.$) feature.$.selectionMode(mode) } - deselected.map(getFeatureById).map(apply('default')) - selection.selected().map(getFeatureById).map(apply(mode)) + deselected + .map(getFeatureById) + .map(apply('default')) + + selection.selected() + .map(getFeatureById) + .filter(Boolean) + .map(apply(mode)) }) return source diff --git a/src/renderer/ol/format.js b/src/renderer/ol/format.js new file mode 100644 index 00000000..1ef10c85 --- /dev/null +++ b/src/renderer/ol/format.js @@ -0,0 +1,19 @@ +import GeoJSON from 'ol/format/GeoJSON' + +export const format = new GeoJSON({ + dataProjection: 'EPSG:3857', + featureProjection: 'EPSG:3857' +}) + +/** + * Note: If source does not include a geometry, geometry of resulting feature is `null`. + */ +export const readFeature = format.readFeature.bind(format) +export const readFeatures = format.readFeatures.bind(format) +export const readGeometry = format.readGeometry.bind(format) +export const writeGeometry = format.writeGeometry.bind(format) +export const writeGeometryObject = format.writeGeometryObject.bind(format) + +// writeFeatureCollection :: [ol/Feature] -> GeoJSON/FeatureCollection +export const writeFeatureCollection = format.writeFeaturesObject.bind(format) +export const writeFeatureObject = format.writeFeatureObject.bind(format) diff --git a/src/renderer/ol/interaction/clone-interaction.js b/src/renderer/ol/interaction/clone-interaction.js index b502dd35..144df2cb 100644 --- a/src/renderer/ol/interaction/clone-interaction.js +++ b/src/renderer/ol/interaction/clone-interaction.js @@ -1,7 +1,7 @@ import uuid from '../../../shared/uuid' import { Translate } from 'ol/interaction' import { MAC } from 'ol/has' -import { writeGeometryObject } from '../../store/FeatureStore' +import { writeGeometryObject } from '../../ol/format' import * as ID from '../../ids' diff --git a/src/renderer/ol/interaction/draw-interaction.js b/src/renderer/ol/interaction/draw-interaction.js index 552327dd..2e58ebf2 100644 --- a/src/renderer/ol/interaction/draw-interaction.js +++ b/src/renderer/ol/interaction/draw-interaction.js @@ -2,7 +2,7 @@ import * as R from 'ramda' import Draw from 'ol/interaction/Draw' import uuid from '../../../shared/uuid' import * as MILSTD from '../../symbology/2525c' -import { writeFeatureObject } from '../../store/FeatureStore' +import { writeFeatureObject } from '../../ol/format' import * as TS from '../ts' import * as EPSG from '../../epsg' import { PI_OVER_2, PI_OVER_4, SQRT_2 } from '../../../shared/Math' diff --git a/src/renderer/ol/interaction/measure/index.js b/src/renderer/ol/interaction/measure/index.js index 6f643fb2..650d32d2 100644 --- a/src/renderer/ol/interaction/measure/index.js +++ b/src/renderer/ol/interaction/measure/index.js @@ -11,7 +11,7 @@ import { styleFN } from './style' import { getLastSegmentCoordinates } from './tools' import { militaryFormat } from '../../../../shared/datetime' import * as ID from '../../../ids' -import { writeFeatureObject } from '../../../store/FeatureStore' +import { writeFeatureObject } from '../../../ol/format' export default ({ map, services }) => { diff --git a/src/renderer/ol/interaction/modify-interaction.js b/src/renderer/ol/interaction/modify-interaction.js index f8b172b6..d322842f 100644 --- a/src/renderer/ol/interaction/modify-interaction.js +++ b/src/renderer/ol/interaction/modify-interaction.js @@ -1,5 +1,5 @@ import { Modify } from './modify' -import { writeGeometryObject } from '../../store/FeatureStore' +import { writeGeometryObject } from '../../ol/format' /** * @param {*} store diff --git a/src/renderer/ol/interaction/translate-interaction.js b/src/renderer/ol/interaction/translate-interaction.js index 7c5cf4bf..0ec06177 100644 --- a/src/renderer/ol/interaction/translate-interaction.js +++ b/src/renderer/ol/interaction/translate-interaction.js @@ -1,6 +1,6 @@ import * as R from 'ramda' import { Translate } from 'ol/interaction' -import { writeFeatureCollection } from '../../store/FeatureStore' +import { writeFeatureCollection } from '../../ol/format' import { noModifierKeys, shiftKeyOnly } from 'ol/events/condition' /** diff --git a/src/renderer/store/FeatureStore.js b/src/renderer/store/FeatureStore.js index 5e0513d1..54a3eb91 100644 --- a/src/renderer/store/FeatureStore.js +++ b/src/renderer/store/FeatureStore.js @@ -1,44 +1,12 @@ /* eslint-disable camelcase */ -import util from 'util' -import GeoJSON from 'ol/format/GeoJSON' import * as Extent from 'ol/extent' -import Emitter from '../../shared/emitter' - -const format = new GeoJSON({ - dataProjection: 'EPSG:3857', - featureProjection: 'EPSG:3857' -}) - -/** - * Note: If source does not include a geometry, geometry of resulting feature is `null`. - */ -export const readFeature = source => format.readFeature(source) -export const readFeatures = source => format.readFeatures(source) -export const readGeometry = source => format.readGeometry(source) -export const writeGeometry = geometry => format.writeGeometry(geometry) -export const writeGeometryObject = geometry => format.writeGeometryObject(geometry) - -// writeFeatureCollection :: [ol/Feature] -> GeoJSON/FeatureCollection -export const writeFeatureCollection = features => format.writeFeaturesObject(features) -export const writeFeatureObject = feature => format.writeFeatureObject(feature) +import { readFeature } from '../ol/format' /** * */ -export function FeatureStore (store, selection, emitter) { +export function FeatureStore (store) { this.store = store - this.selection = selection - this.emitter = emitter - this.features = {} - this.styleProperties = {} // global (default), layer and feature style properties -} - -util.inherits(FeatureStore, Emitter) - -/** - * - */ -FeatureStore.prototype.bootstrap = async function () { } @@ -52,18 +20,3 @@ FeatureStore.prototype.center = async function (key) { const extent = feature.getGeometry()?.getExtent() return Extent.getCenter(extent) } - - -/** - * - */ -FeatureStore.prototype.feature = function (key) { - return this.features[key] -} - - -/** - * - */ -FeatureStore.prototype.wrapFeature = function (feature) { -} diff --git a/src/renderer/store/Store.js b/src/renderer/store/Store.js index cf39dd83..8a48352b 100644 --- a/src/renderer/store/Store.js +++ b/src/renderer/store/Store.js @@ -6,7 +6,7 @@ import * as L from '../../shared/level' import { PartitionDOWN } from '../../shared/level/PartitionDOWN' import * as TS from '../ol/ts' import { transform, geometryType } from '../model/geometry' -import { readGeometry } from '../store/FeatureStore' +import { readGeometry } from '../ol/format' import { bbox } from './geometry' /** diff --git a/src/renderer/store/options/place.js b/src/renderer/store/options/place.js index b84bf077..d8339b78 100644 --- a/src/renderer/store/options/place.js +++ b/src/renderer/store/options/place.js @@ -3,7 +3,7 @@ import * as Extent from 'ol/extent' import * as Sphere from 'ol/sphere' import { toLonLat } from 'ol/proj' import * as ID from '../../ids' -import { readGeometry } from '../../store/FeatureStore' +import { readGeometry } from '../../ol/format' export default async function (id) { const keys = [R.identity, ID.tagsId] From 7b91aee2dffbe01ce51683014d7394952ffaf0e8 Mon Sep 17 00:00:00 2001 From: dehmer Date: Sat, 13 Jul 2024 16:31:11 +0200 Subject: [PATCH 55/73] webpack/build: consume own sourcemaps. --- package-lock.json | 25 +++++++++++++++++++++++-- package.json | 1 + webpack.config.js | 23 +++++++++++++++++++---- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6bbe005..44712b9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,6 +70,7 @@ "mocha": "^10.6.0", "sass": "^1.77.6", "sass-loader": "^14.2.1", + "source-map-loader": "^5.0.0", "style-loader": "^4.0.0", "webpack": "^5.92.1", "webpack-cli": "^5.0.0", @@ -2937,11 +2938,11 @@ }, "node_modules/@syncpoint/signal": { "version": "1.2.0", - "resolved": "git+ssh://git@github.com/syncpoint/signal.git#e9fc0d69a429e989fd8bc06588a1d58ae1e0e62c" + "resolved": "git+ssh://git@github.com/syncpoint/signal.git#c77316f880a38a9d1447b137984ae5ae2478967b" }, "node_modules/@syncpoint/signs": { "version": "1.1.0", - "resolved": "git+ssh://git@github.com/syncpoint/signs.git#8449525ee57f75f6705e923f867051656f5e65a7", + "resolved": "git+ssh://git@github.com/syncpoint/signs.git#127ca0dcefe73ec3ebc8cf5ab0841eb622930ce6", "dependencies": { "ramda": "^0.30.1", "svg-path-bbox": "^2.0.0" @@ -15040,6 +15041,26 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", diff --git a/package.json b/package.json index b0cf6481..992005e3 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "mocha": "^10.6.0", "sass": "^1.77.6", "sass-loader": "^14.2.1", + "source-map-loader": "^5.0.0", "style-loader": "^4.0.0", "webpack": "^5.92.1", "webpack-cli": "^5.0.0", diff --git a/webpack.config.js b/webpack.config.js index 8091e387..a88236fa 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -47,6 +47,23 @@ const RULES = { font: { test: /\.(eot|svg|ttf|woff|woff2)$/, type: 'asset/resource' + }, + + sourcemap: { + test: /\.js$/, + enforce: "pre", + use: [ + { + loader: "source-map-loader", + options: { + filterSourceMappingUrl: (url, resourcePath) => { + // Consume own (@syncpoint) sourcemaps; remove others. + if (/@syncpoint/g.test(resourcePath)) return 'consume' + else return 'remove' + } + } + } + ] } } @@ -123,10 +140,8 @@ const devServer = env => { } const devtool = env => { - if (env.production) return ({}) // no source maps for production - return ({ - devtool: 'cheap-source-map' - }) + if (env.production) return ({ devtool: 'source-map' }) + else return ({ devtool: 'eval-source-map' }) } module.exports = (env, argv) => { From 3fe16e351ee924e30fe626a2546621befeda4b52 Mon Sep 17 00:00:00 2001 From: dehmer Date: Sun, 14 Jul 2024 06:37:50 +0200 Subject: [PATCH 56/73] feature-source: fixed issue. --- src/renderer/model/sources/featureSource.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index 6b4d8356..272be888 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -113,6 +113,7 @@ export const featureSource = services => { }) const getFeatureById = source.getFeatureById.bind(source) + const getFeaturesById = ids => ids.map(getFeatureById).filter(Boolean) ;(async () => { state.styles = await store.dictionary('style+') @@ -169,19 +170,9 @@ export const featureSource = services => { ? 'multiselect' : 'singleselect' - const apply = mode => feature => { - console.log(feature, feature.$) - feature.$.selectionMode(mode) - } - - deselected - .map(getFeatureById) - .map(apply('default')) - - selection.selected() - .map(getFeatureById) - .filter(Boolean) - .map(apply(mode)) + const apply = mode => feature => feature.$.selectionMode(mode) + getFeaturesById(deselected).map(apply('default')) + getFeaturesById(selection.selected()).map(apply(mode)) }) return source From 1a30b98700b9376221e9b814d6664642934ce343 Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 15 Jul 2024 08:47:50 +0200 Subject: [PATCH 57/73] range fan: fixed label update (#36). --- src/renderer/components/properties/Radius.js | 3 ++- src/renderer/components/properties/geometries.js | 6 ++++++ src/renderer/model/sources/featureSource.js | 9 +++++++-- src/renderer/ol/style/_evalSync.js | 4 ++-- src/renderer/ol/style/corridor.js | 1 - src/renderer/ol/style/graphics.js | 14 +++++++++++++- src/renderer/ol/style/multipoint.js | 2 -- src/renderer/symbology/2525c.js | 2 +- 8 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/renderer/components/properties/Radius.js b/src/renderer/components/properties/Radius.js index d1e8cac7..e526676f 100644 --- a/src/renderer/components/properties/Radius.js +++ b/src/renderer/components/properties/Radius.js @@ -20,10 +20,11 @@ const TextProperty = textProperty({ const properties = geom.circleProperties(jtsGeometry) properties.am = value const circle = geom.circle(jtsGeometry, properties) + const geometry = writeGeometryObject(write(circle)) return { ...feature, - geometry: writeGeometryObject(write(circle)), + geometry, properties: { ...feature.properties, am: value diff --git a/src/renderer/components/properties/geometries.js b/src/renderer/components/properties/geometries.js index 1ab60f25..59ba9193 100644 --- a/src/renderer/components/properties/geometries.js +++ b/src/renderer/components/properties/geometries.js @@ -40,6 +40,12 @@ export const corridorProperties = geometry => { } } +export const GeometryProperties = { + RECTANGLE: rectangleProperties, + CIRCLE: circleProperties, + CORRIDOR: corridorProperties +} + const unitSquare = TS.polygon([ [-1, 1], [1, 1], [1, -1], [-1, -1], [-1, 1] ].map(TS.coordinate)) diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index 272be888..455ae636 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -58,7 +58,9 @@ const readFeature = R.curry((state, source) => { } feature.on('change', ({ target }) => { - target.$.geometry(target.getGeometry()) + const { geometry, ...properties } = target.getProperties() + target.$.properties(properties) + target.$.geometry(geometry) }) return feature @@ -156,7 +158,10 @@ export const featureSource = services => { feature.on(({ type, key, value }) => { let feature = getFeatureById(key) if (type === 'del') source.removeFeature(feature) - else if (feature) feature.$.properties(value.properties) + else if (feature) { + feature.setProperties(value.properties) + feature.setGeometry(format.readGeometry(value.geometry)) + } else { feature = readFeature(state, { id: key, ...value }) source.addFeature(feature) diff --git a/src/renderer/ol/style/_evalSync.js b/src/renderer/ol/style/_evalSync.js index f7460aff..689e0012 100644 --- a/src/renderer/ol/style/_evalSync.js +++ b/src/renderer/ol/style/_evalSync.js @@ -33,7 +33,7 @@ const evalSync = context => { return replaceAll } -export default (sidc, modifiers) => { +export default (sidc, props1, props2) => { const code = echelonCode(sidc) const echelon = (code === '*' || code === '-') @@ -41,7 +41,7 @@ export default (sidc, modifiers) => { : echelons[code]?.text return evalSync({ - modifiers, + modifiers: { ...props1, ...props2 }, echelon }) } diff --git a/src/renderer/ol/style/corridor.js b/src/renderer/ol/style/corridor.js index a8e446ec..a2537afa 100644 --- a/src/renderer/ol/style/corridor.js +++ b/src/renderer/ol/style/corridor.js @@ -7,7 +7,6 @@ import _shape from './_shape' import _selection from './_selection' const specifics = $ => { - $.jtsGeometry = $.geometry.ap($.read) $.context = Signal.link(_context, [$.jtsGeometry, $.resolution]) $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) $.selection = Signal.link(_selection, [$.selectionMode, $.jtsGeometry]) diff --git a/src/renderer/ol/style/graphics.js b/src/renderer/ol/style/graphics.js index d89d54d6..7730e47d 100644 --- a/src/renderer/ol/style/graphics.js +++ b/src/renderer/ol/style/graphics.js @@ -1,19 +1,31 @@ import * as R from 'ramda' import Signal from '@syncpoint/signal' import transform from './_transform' +import { specialization } from '../../symbology/2525c' +import { GeometryProperties } from '../../components/properties/geometries' import _rewrite from './_rewrite' import _evalSync from './_evalSync' import _clip from './_clip' +const EMPTY_OBJECT = {} export default specifics => $ => { const [read, write, pointResolution] = transform($.geometry) $.read = read $.rewrite = write.map(fn => xs => xs.map(_rewrite(fn))) $.pointResolution = pointResolution $.resolution = $.centerResolution.ap($.pointResolution) + $.jtsGeometry = $.geometry.ap($.read) $.clip = $.resolution.map(_clip) - $.evalSync = Signal.link(_evalSync, [$.sidc, $.properties]) + + // Derive additional properties from geometry, + // which might be used in labels (an, am). + $.specialization = $.sidc.map(sidc => specialization(sidc) || null) + $.geometryProperties = Signal.link((specialization, geometry) => { + const fn = GeometryProperties[specialization] + return fn ? fn(geometry) : EMPTY_OBJECT + }, [$.specialization, $.jtsGeometry]) + $.evalSync = Signal.link(_evalSync, [$.sidc, $.properties, $.geometryProperties]) specifics($) diff --git a/src/renderer/ol/style/multipoint.js b/src/renderer/ol/style/multipoint.js index ce0131e3..31492b09 100644 --- a/src/renderer/ol/style/multipoint.js +++ b/src/renderer/ol/style/multipoint.js @@ -17,10 +17,8 @@ const _pointBuffer = geometry => { } const specifics = $ => { - $.jtsGeometry = $.geometry.ap($.read) $.context = Signal.link(_context, [$.jtsGeometry, $.resolution]) $.placement = $.jtsGeometry.map(_pointBuffer).map(placement) - $.shape = $.context.ap($.parameterizedSIDC.map(_shape(styles))) $.selection = Signal.link(_selection, [$.selectionMode, $.jtsGeometry]) $.labels = $.parameterizedSIDC diff --git a/src/renderer/symbology/2525c.js b/src/renderer/symbology/2525c.js index 0f990844..93eb22d0 100644 --- a/src/renderer/symbology/2525c.js +++ b/src/renderer/symbology/2525c.js @@ -177,5 +177,5 @@ export const specialization = sidc => { else if (geometry && geometry.layout === 'rectangle') return 'RECTANGLE' else if (geometry && geometry.layout === 'circle') return 'CIRCLE' else if (geometry && geometry.layout === 'corridor') return 'CORRIDOR' - else return null + else return } From 85613f639aeb9df14c3efce646cea75ca4ab2c8d Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 15 Jul 2024 09:40:16 +0200 Subject: [PATCH 58/73] OSD: fixed missed updates (#24). --- src/renderer/components/OSD.js | 1 + src/renderer/model/OSDDriver.js | 14 ++++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/renderer/components/OSD.js b/src/renderer/components/OSD.js index 38f977eb..234d0245 100644 --- a/src/renderer/components/OSD.js +++ b/src/renderer/components/OSD.js @@ -20,6 +20,7 @@ export const OSD = () => { React.useEffect(() => { emitter.on('osd', dispatch) + emitter.emit('osd-mounted') }, [emitter]) return
diff --git a/src/renderer/model/OSDDriver.js b/src/renderer/model/OSDDriver.js index d5b8bc40..e7585763 100644 --- a/src/renderer/model/OSDDriver.js +++ b/src/renderer/model/OSDDriver.js @@ -2,7 +2,7 @@ import { toLonLat } from 'ol/proj' import { LatLon } from 'geodesy/mgrs.js' import Dms from 'geodesy/dms.js' import { militaryFormat } from '../../shared/datetime' -import { isDefaultId } from '../ids' +import { isDefaultId, isLayerId } from '../ids' Dms.separator = ' ' @@ -32,16 +32,16 @@ export const OSDDriver = function (projectUUID, emitter, preferencesStore, proje this.projectStore = projectStore this.store = store - ;(async () => { + emitter.on('osd-mounted', async () => { this.coordinatesFormat = await preferencesStore.get('coordinates-format', 'MGRS') this.updateProjectName() this.updateDefaultLayer() - })() + }) setInterval(this.updateDateTime.bind(this), 1000) store.on('batch', ({ operations }) => { - const update = operations.some(({ key }) => isDefaultId(key)) + const update = operations.some(({ key }) => isDefaultId(key) || isLayerId(key)) if (update) this.updateDefaultLayer() }) @@ -72,11 +72,9 @@ OSDDriver.prototype.updateProjectName = async function () { } OSDDriver.prototype.updateDefaultLayer = async function () { - const { store } = this - - const layerId = await store.defaultLayerId() + const layerId = await this.store.defaultLayerId() if (layerId) { - const layer = await store.value(layerId) + const layer = await this.store.value(layerId) this.emitter.emit('osd', { message: layer.name, cell: 'A2' }) } else { this.emitter.emit('osd', { message: '', cell: 'A2' }) From 9997e952ac7b8c2274e337f78fc8b88a27e9849e Mon Sep 17 00:00:00 2001 From: dehmer Date: Mon, 15 Jul 2024 17:27:49 +0200 Subject: [PATCH 59/73] moved geometry type signal. --- paperwork/polygon-styles.dot | 80 ------------------------ paperwork/polygon-styles.md | 80 ++++++++++++++++++++++++ paperwork/polygon-styles.pdf | Bin 33217 -> 0 bytes paperwork/rules.dot | 101 ------------------------------- paperwork/rules.pdf | Bin 35129 -> 0 bytes src/renderer/ol/style/measure.js | 1 + src/renderer/ol/style/styles.js | 1 - 7 files changed, 81 insertions(+), 182 deletions(-) delete mode 100644 paperwork/polygon-styles.dot create mode 100644 paperwork/polygon-styles.md delete mode 100644 paperwork/polygon-styles.pdf delete mode 100644 paperwork/rules.dot delete mode 100644 paperwork/rules.pdf diff --git a/paperwork/polygon-styles.dot b/paperwork/polygon-styles.dot deleted file mode 100644 index 9d73220c..00000000 --- a/paperwork/polygon-styles.dot +++ /dev/null @@ -1,80 +0,0 @@ -digraph polygon { - node [shape=record]; - - // inputs - globalStyle [label="globalStyle\n\{k: v\}", color=blue] - layerStyle [label="layerStyle\n\{k: v\}", color=blue] - featureStyle [label="featureStyle\n\{k: v\}", color=blue] - feature [label="feature\nol/Feature", color=blue] - resolution [label="resolution\nNumber", color=blue] - properties [label="properties\n\{k: v\}"]; - geometry [label="geometry\nol/geom/Geometry"] - sidc [label="sidc\nString"] - colorScheme [label="colorScheme\nString"] - schemeStyle [label="schemeStyle\n\{k: v\}"] - effectiveStyle [label="effectiveStyle\n\{k: v\}"] - read [label="read\nol/Geometry =\> jts/Geomery"] - write [label="write\njts/Geometry =\> ol/Geomery"] - _pointResolution [label="_pointResolution\nNumber =\> Number"] - pointResolution [label="pointResolution\nNumber"] - // utmGeometry [label="utmGeometry\njts/Geometry"] - parameterizedSIDC [label="parameterizedSIDC\nString"] - labels [label="labels\n[\{k: v\}]"] - evalSync [label="evalSync\n\{k: v\} =\> \{k: v\}"] - simplifiedGeometry [label="simplifiedGeometry\nol/Geometry"] - lineSmoothing [label="lineSmoothing\nBoolean"] - smoothenedGeometry [label="smoothenedGeometry\nBoolean"] - utmSmoothenedGeometry [label="utmSmoothenedGeometry\njts/Geometry"] - context [label="context\n\{k: v\}"] - shape [label="shape\n\{k: v\}"] - placement [label="placement\n\{k: v\} =\> \{k: v\}"] - styles [label="styles\n[\{k: v\}]"] - style [label="style\nol/Style", color=red] - - // shared - feature -> properties - properties -> geometry - properties -> sidc - globalStyle -> colorScheme - layerStyle -> colorScheme - featureStyle -> colorScheme - sidc -> schemeStyle - colorScheme -> schemeStyle - globalStyle -> effectiveStyle - schemeStyle -> effectiveStyle - layerStyle -> effectiveStyle - featureStyle -> effectiveStyle - effectiveStyle -> styleRegistry - - // shape - geometry -> read - geometry -> write - geometry -> _pointResolution - _pointResolution -> pointResolution - resolution -> pointResolution - read -> utmGeometry - geometry -> utmGeometry - sidc -> parameterizedSIDC - properties -> evalSync - sidc -> evalSync - geometry -> simplifiedGeometry - resolution -> simplifiedGeometry - effectiveStyle -> lineSmoothing - lineSmoothing -> smoothenedGeometry - simplifiedGeometry -> smoothenedGeometry - smoothenedGeometry -> utmSmoothenedGeometry - read -> utmSmoothenedGeometry - utmSmoothenedGeometry -> placement - utmSmoothenedGeometry -> context - parameterizedSIDC -> labels - evalSync -> labels - placement -> labels - pointResolution -> context - context -> shape - parameterizedSIDC -> shape - labels -> styles - shape -> styles - styles -> style - styleRegistry -> style - write -> style -} \ No newline at end of file diff --git a/paperwork/polygon-styles.md b/paperwork/polygon-styles.md new file mode 100644 index 00000000..0204dcb3 --- /dev/null +++ b/paperwork/polygon-styles.md @@ -0,0 +1,80 @@ +```mermaid +graph LR; + +classDef common fill:#ff9; +classDef graphics fill:#f99; +classDef polygon fill:#efe; + +%% Inputs +properties[[properties]]; +geometry[[geometry]]; +globalStyle[[globalStyle]]; +layerStyle[[layerStyle]]; +featureStyle[[featureStyle]]; +centerResolution[[centerResolution]]; +selectionMode[[selectionMode]]; + +styleFactory[[styleFactory]]; + +%% Common +properties --> sidc:::common; +sidc --> parameterizedSIDC:::common; +globalStyle --> colorScheme:::common; +layerStyle --> colorScheme; +featureStyle --> colorScheme; +sidc --> schemeStyle:::common; +colorScheme --> schemeStyle; +globalStyle --> effectiveStyle:::common; +schemeStyle --> effectiveStyle; +layerStyle --> effectiveStyle; +featureStyle --> effectiveStyle; +effectiveStyle --> styleRegistry:::common; + +%% Graphics +geometry --> read:::graphics; +geometry --> rewrite:::graphics; +geometry --> pointResolution:::graphics; +centerResolution --> resolution:::graphics; +pointResolution --> resolution; +geometry --> jtsGeometry:::graphics; +read --> jtsGeometry; +resolution --> clip:::graphics; +sidc --> specialization:::graphics; +specialization --> geometryProperties:::graphics; +jtsGeometry --> geometryProperties; +sidc --> evalSync:::graphics; +properties --> evalSync; +geometryProperties --> evalSync; + +%% Polygon +geometry --> simplifiedGeometry:::polygon; +centerResolution --> simplifiedGeometry; +simplifiedGeometry --> jtsSimplifiedGeometry:::polygon; +read --> jtsSimplifiedGeometry; +effectiveStyle --> lineSmoothing:::polygon; +simplifiedGeometry --> smoothenedGeometry:::polygon; +lineSmoothing --> smoothenedGeometry; +smoothenedGeometry --> jtsSmoothenedGeometry:::polygon; +read --> jtsSmoothenedGeometry; +jtsSmoothenedGeometry --> context:::polygon; +resolution --> context; +jtsSmoothenedGeometry --> placement:::polygon; +context --> shape:::polygon; +parameterizedSIDC --> shape; +selectionMode --> selection:::polygon; +jtsSimplifiedGeometry --> selection; +parameterizedSIDC --> labels:::polygon; +placement --> labels; +shape --> styles:::polygon; +labels --> styles; +selection --> styles; + +%% Final +A{{style}} +styles --> A; +styleRegistry --> A; +evalSync --> A; +clip --> A; +rewrite --> A; +styleFactory --> A; +``` \ No newline at end of file diff --git a/paperwork/polygon-styles.pdf b/paperwork/polygon-styles.pdf deleted file mode 100644 index fae59ee8037bbfa3e9462fc866e17a93e561603f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33217 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 diff --git a/paperwork/rules.dot b/paperwork/rules.dot deleted file mode 100644 index b44ae688..00000000 --- a/paperwork/rules.dot +++ /dev/null @@ -1,101 +0,0 @@ -digraph structs { - ranksep=1; - { - node [shape=plaintext, fontsize=16]; - Input -> I -> II -> III -> IV -> V -> VI; - } - - { rank = same; - Input; - node [shape=record]; - properties [color=red]; - }; - - { rank = same; - I; - node [shape=record]; - INPUT_A [color=red,label="{{ - globalStyle| - layerStyle| - featureStyle - }}"]; - - PROPERTIES [label="{{ - sidc| - parameterized\nSIDC| - modifiers - }}"]; - }; - - { rank = same; - II; - node [shape=record]; - INPUT_B [color=red,label="{{ - key| - centerResolution - }}"]; - - EFFECTIVE [label="{{ - effectiveStyle| - smoothen - }}"]; - - evalSync; - - node [shape=record]; - STYLES [color=blue,label="{{ - dynamicStyle| - staticStyles - }}"]; - }; - - { rank = same; - III; - node [shape=record]; - GEOMETRY [label="{{ - simplifiedGeometry| - geometry - }}" - ]; - }; - - { rank = same; - IV; - node [shape=box]; - mode [color=red]; - placement [color=blue]; - }; - - { rank = same; - V; - node [shape=box]; - styles; selectedStyles; - }; - - { rank = same; - VI; - node [shape=box]; - style [color=green]; - }; - - properties -> PROPERTIES; - PROPERTIES:sidc:s -> EFFECTIVE:ne; - INPUT_A:globalStyle -> EFFECTIVE:n; - INPUT_A:layerStyle -> EFFECTIVE:n; - INPUT_A:featureStyle -> EFFECTIVE:n; - PROPERTIES:parameterizedSIDC -> STYLES:n; - PROPERTIES:modifiers -> evalSync:n; - EFFECTIVE:smoothen:s -> GEOMETRY:n; - INPUT_B:key -> GEOMETRY:n; - INPUT_B:centerResolution -> GEOMETRY:n; - GEOMETRY:geometry:s -> placement:n; - STYLES:dynamicStyle -> styles; - STYLES:staticStyles -> styles; - mode -> selectedStyles; - GEOMETRY:simplifiedGeometry -> selectedStyles:n; - placement -> styles; - evalSync:s -> styles; - styles -> style; - EFFECTIVE:effectiveStyle:s -> style; - selectedStyles -> style; -} diff --git a/paperwork/rules.pdf b/paperwork/rules.pdf deleted file mode 100644 index d921ddf6248eb6780248f1181bc7c36d34c233f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35129 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 { + $.geometryType = $.geometry.map(geometry => geometry.getType()) $.selected = $.selectionMode.map(mode => mode !== 'default') $.baseStyle = $.selected.map(baseStyle) $.styleFN = $.geometryType.map(type => STYLES[type]) diff --git a/src/renderer/ol/style/styles.js b/src/renderer/ol/style/styles.js index 9ecfd048..5c35d714 100644 --- a/src/renderer/ol/style/styles.js +++ b/src/renderer/ol/style/styles.js @@ -21,7 +21,6 @@ import _effectiveStyle from './_effectiveStyle' export default feature => { const { $ } = feature - $.geometryType = $.geometry.map(geometry => geometry.getType()) $.sidc = $.properties.map(R.prop('sidc')) $.parameterizedSIDC = $.sidc.map(parameterized) $.colorScheme = Signal.link(_colorScheme, [$.globalStyle, $.layerStyle, $.featureStyle]) From d31515ae373a8f9a1ec26e2990b17e173244cd2e Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 16 Jul 2024 06:49:18 +0200 Subject: [PATCH 60/73] clean-up. --- package-lock.json | 2 +- package.json | 2 +- src/renderer/model/sources/featureSource.js | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 44712b9d..f78e6a9a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "dependencies": { "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.0", - "@syncpoint/signal": "github:syncpoint/signal#vite", + "@syncpoint/signal": "github:syncpoint/signal", "@syncpoint/signs": "github:syncpoint/signs", "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", diff --git a/package.json b/package.json index 992005e3..ee6cbc41 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "dependencies": { "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.0", - "@syncpoint/signal": "github:syncpoint/signal#vite", + "@syncpoint/signal": "github:syncpoint/signal", "@syncpoint/signs": "github:syncpoint/signs", "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index 455ae636..575dbb60 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -161,8 +161,7 @@ export const featureSource = services => { else if (feature) { feature.setProperties(value.properties) feature.setGeometry(format.readGeometry(value.geometry)) - } - else { + } else { feature = readFeature(state, { id: key, ...value }) source.addFeature(feature) } From c9a1cdf6a4fe138eb8ddd5678bd330233d4e7089 Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 16 Jul 2024 07:50:07 +0200 Subject: [PATCH 61/73] chore: updated build dependencies. --- package-lock.json | 242 ++++++++++++++++++++++------------------------ package.json | 12 +-- 2 files changed, 124 insertions(+), 130 deletions(-) diff --git a/package-lock.json b/package-lock.json index f78e6a9a..66256bc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,9 +49,9 @@ "uniqolor": "^1.1.1" }, "devDependencies": { - "@babel/core": "^7.21.4", - "@babel/eslint-parser": "^7.18.2", - "@babel/preset-env": "^7.21.4", + "@babel/core": "^7.24.9", + "@babel/eslint-parser": "^7.24.8", + "@babel/preset-env": "^7.24.8", "@babel/preset-react": "^7.17.12", "@babel/register": "^7.17.7", "babel-loader": "^9.1.0", @@ -61,18 +61,18 @@ "electron-builder": "^24.6.4", "electron-updater": "^6.1.4", "eslint-config-standard": "^17.0.0", - "eslint-plugin-react": "^7.34.3", + "eslint-plugin-react": "^7.34.4", "eslint-plugin-react-hooks": "^4.6.0", "file-loader": "^6.2.0", "html-webpack-plugin": "^5.5.0", "jsdoc": "^4.0.0", "memdown": "^6.1.1", "mocha": "^10.6.0", - "sass": "^1.77.6", + "sass": "^1.77.8", "sass-loader": "^14.2.1", "source-map-loader": "^5.0.0", "style-loader": "^4.0.0", - "webpack": "^5.92.1", + "webpack": "^5.93.0", "webpack-cli": "^5.0.0", "webpack-dev-server": "^5.0.4", "yaml": "^2.1.3" @@ -105,30 +105,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", - "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz", + "integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", - "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", + "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helpers": "^7.24.7", - "@babel/parser": "^7.24.7", + "@babel/generator": "^7.24.9", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-module-transforms": "^7.24.9", + "@babel/helpers": "^7.24.8", + "@babel/parser": "^7.24.8", "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.9", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -144,9 +144,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.7.tgz", - "integrity": "sha512-SO5E3bVxDuxyNxM5agFv480YA2HO6ohZbGxbazZdIk3KQOPOGVNw6q78I9/lbviIf95eq6tPozeYnJLbjnC8IA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.8.tgz", + "integrity": "sha512-nYAikI4XTGokU2QX7Jx+v4rxZKhKivaQaREZjuW3mrJrbdWJ5yUfohnoUULge+zEEaKjPYNxhoRgUKktjXtbwA==", "dev": true, "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", @@ -162,12 +162,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", - "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.9.tgz", + "integrity": "sha512-G8v3jRg+z8IwY1jHFxvCNhOPYPterE4XljNgdGTYfSTtzzwjIswIzIaSPSLs3R7yFuqnqNeay5rjICfqVr+/6A==", "dev": true, "dependencies": { - "@babel/types": "^7.24.7", + "@babel/types": "^7.24.9", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -202,14 +202,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", - "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", + "integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -337,9 +337,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", - "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz", + "integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.24.7", @@ -368,9 +368,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -449,9 +449,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -467,9 +467,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, "engines": { "node": ">=6.9.0" @@ -491,13 +491,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", + "integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", "dev": true, "dependencies": { "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -519,9 +519,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", + "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -970,16 +970,16 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", - "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.8.tgz", + "integrity": "sha512-VXy91c47uujj758ud9wx+OMgheXm4qJfyhj1P18YvlrQkNOSrwsteHk+EFS3OMGfhMhpZa0A+81eE7G4QC+3CA==", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-function-name": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", "@babel/helper-replace-supers": "^7.24.7", "@babel/helper-split-export-declaration": "^7.24.7", "globals": "^11.1.0" @@ -1008,12 +1008,12 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", - "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1213,13 +1213,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", - "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", "@babel/helper-simple-access": "^7.24.7" }, "engines": { @@ -1377,12 +1377,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz", - "integrity": "sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, @@ -1615,12 +1615,12 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", - "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -1693,15 +1693,15 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.7.tgz", - "integrity": "sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.8.tgz", + "integrity": "sha512-vObvMZB6hNWuDxhSaEPTKCwcqkAIuDtE+bQGn4XMXne1DSLzFVY8Vmj1bm+mUQXYNN8NmaQEO+r8MMbzPr1jBQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", + "@babel/compat-data": "^7.24.8", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", @@ -1732,9 +1732,9 @@ "@babel/plugin-transform-block-scoping": "^7.24.7", "@babel/plugin-transform-class-properties": "^7.24.7", "@babel/plugin-transform-class-static-block": "^7.24.7", - "@babel/plugin-transform-classes": "^7.24.7", + "@babel/plugin-transform-classes": "^7.24.8", "@babel/plugin-transform-computed-properties": "^7.24.7", - "@babel/plugin-transform-destructuring": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-dotall-regex": "^7.24.7", "@babel/plugin-transform-duplicate-keys": "^7.24.7", "@babel/plugin-transform-dynamic-import": "^7.24.7", @@ -1747,7 +1747,7 @@ "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-member-expression-literals": "^7.24.7", "@babel/plugin-transform-modules-amd": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-modules-systemjs": "^7.24.7", "@babel/plugin-transform-modules-umd": "^7.24.7", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", @@ -1757,7 +1757,7 @@ "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-object-super": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", @@ -1768,7 +1768,7 @@ "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-template-literals": "^7.24.7", - "@babel/plugin-transform-typeof-symbol": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", "@babel/plugin-transform-unicode-escapes": "^7.24.7", "@babel/plugin-transform-unicode-property-regex": "^7.24.7", "@babel/plugin-transform-unicode-regex": "^7.24.7", @@ -1777,7 +1777,7 @@ "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.4", "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.31.0", + "core-js-compat": "^3.37.1", "semver": "^6.3.1" }, "engines": { @@ -1872,19 +1872,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", + "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", + "@babel/generator": "^7.24.8", "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-function-name": "^7.24.7", "@babel/helper-hoist-variables": "^7.24.7", "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/parser": "^7.24.8", + "@babel/types": "^7.24.8", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1893,12 +1893,12 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", + "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-string-parser": "^7.24.8", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, @@ -7686,9 +7686,9 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.34.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz", - "integrity": "sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==", + "version": "7.34.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.4.tgz", + "integrity": "sha512-Np+jo9bUwJNxCsT12pXtrGhJgT3T44T1sHhn1Ssr42XFn8TES0267wPGo5nNrMHi8qkyimDAX2BUmkf9pSaVzA==", "dev": true, "dependencies": { "array-includes": "^3.1.8", @@ -7699,16 +7699,17 @@ "doctrine": "^2.1.0", "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", + "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.8", "object.fromentries": "^2.0.8", - "object.hasown": "^1.1.4", "object.values": "^1.2.0", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11" + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" }, "engines": { "node": ">=4" @@ -12651,23 +12652,6 @@ "node": ">= 0.4" } }, - "node_modules/object.hasown": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", - "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.values": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", @@ -14314,9 +14298,9 @@ } }, "node_modules/sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", + "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", "dev": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -15327,6 +15311,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -16385,9 +16379,9 @@ "integrity": "sha512-BSR9wyRsy/KOValMgd5kMyr3JzpdeoR9KVId8u5GVlTTAtNChlsE4yTxeY7zMdNSyOmoKBv8NH2qeRY9Tg+IaA==" }, "node_modules/webpack": { - "version": "5.92.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.1.tgz", - "integrity": "sha512-JECQ7IwJb+7fgUFBlrJzbyu3GEuNBcdqr1LD7IbSzwkSmIevTm8PF+wej3Oxuz/JFBUZ6O1o43zsPkwm1C4TmA==", + "version": "5.93.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", + "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", "dev": true, "dependencies": { "@types/eslint-scope": "^3.7.3", diff --git a/package.json b/package.json index ee6cbc41..dcc636b3 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,9 @@ }, "license": "AGPLv3", "devDependencies": { - "@babel/core": "^7.21.4", - "@babel/eslint-parser": "^7.18.2", - "@babel/preset-env": "^7.21.4", + "@babel/core": "^7.24.9", + "@babel/eslint-parser": "^7.24.8", + "@babel/preset-env": "^7.24.8", "@babel/preset-react": "^7.17.12", "@babel/register": "^7.17.7", "babel-loader": "^9.1.0", @@ -40,18 +40,18 @@ "electron-builder": "^24.6.4", "electron-updater": "^6.1.4", "eslint-config-standard": "^17.0.0", - "eslint-plugin-react": "^7.34.3", + "eslint-plugin-react": "^7.34.4", "eslint-plugin-react-hooks": "^4.6.0", "file-loader": "^6.2.0", "html-webpack-plugin": "^5.5.0", "jsdoc": "^4.0.0", "memdown": "^6.1.1", "mocha": "^10.6.0", - "sass": "^1.77.6", + "sass": "^1.77.8", "sass-loader": "^14.2.1", "source-map-loader": "^5.0.0", "style-loader": "^4.0.0", - "webpack": "^5.92.1", + "webpack": "^5.93.0", "webpack-cli": "^5.0.0", "webpack-dev-server": "^5.0.4", "yaml": "^2.1.3" From dbd2042433cf77689da545a9b4c51f0029f48ee5 Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 16 Jul 2024 07:55:13 +0200 Subject: [PATCH 62/73] chore: updated path-to-regexp@7.1.0. --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 66256bc3..4b612624 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,7 @@ "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", - "path-to-regexp": "^7.0.0", + "path-to-regexp": "^7.1.0", "proj4": "^2.8.0", "ramda": "^0.30.1", "rbush": "^4.0.0", @@ -13012,9 +13012,9 @@ } }, "node_modules/path-to-regexp": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-7.0.0.tgz", - "integrity": "sha512-58Y94bQqF3zBIASFNiufRPH1NfgZth1qwZ35radL87sg8pgbVqr6uikAhqZtFD+w65MGH6SWnY/ly3GbrM4fbg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-7.1.0.tgz", + "integrity": "sha512-ZToe+MbUF4lBqk6dV8GKot4DKfzrxXsplOddH8zN3YK+qw9/McvP7+4ICjZvOne0jQhN4eJwHsX6tT0Ns19fvw==", "engines": { "node": ">=16" } diff --git a/package.json b/package.json index dcc636b3..b92d3c8f 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", - "path-to-regexp": "^7.0.0", + "path-to-regexp": "^7.1.0", "proj4": "^2.8.0", "ramda": "^0.30.1", "rbush": "^4.0.0", From 482aaeaad4eb349d81499df11d19d27cbc3cc942 Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 16 Jul 2024 08:00:07 +0200 Subject: [PATCH 63/73] chore: updated minisearch@7.0.0 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b612624..ed1545f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "leveldown": "^6.1.1", "levelup": "^5.0.1", "luxon": "^3.1.0", - "minisearch": "^6.0.1", + "minisearch": "^7.0.0", "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", @@ -11830,9 +11830,9 @@ "dev": true }, "node_modules/minisearch": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", - "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==" + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.0.0.tgz", + "integrity": "sha512-0OIJ3hUE+YBJNruDCqbTMFmk/IoB1CpZzuGfl11khFIel66ew9UoLF/+gfq3bdyrneqr3P7BTjFZApUbmk+9Dg==" }, "node_modules/minizlib": { "version": "2.1.2", diff --git a/package.json b/package.json index b92d3c8f..393e6ffd 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "leveldown": "^6.1.1", "levelup": "^5.0.1", "luxon": "^3.1.0", - "minisearch": "^6.0.1", + "minisearch": "^7.0.0", "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", From b566abf163818a540bdcefe971cc6719c6faa1cf Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 16 Jul 2024 08:05:42 +0200 Subject: [PATCH 64/73] chore: updated electron@31.2.1 --- electron-builder.yml | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/electron-builder.yml b/electron-builder.yml index 08299f49..2eeb465f 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -35,4 +35,4 @@ publish: - provider: github releaseType: release -electronVersion: 31.1.0 +electronVersion: 31.2.1 diff --git a/package-lock.json b/package-lock.json index ed1545f3..2408fa53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,7 @@ "babel-loader": "^9.1.0", "c8": "^10.1.2", "css-loader": "^7.1.2", - "electron": "^31.1.0", + "electron": "^31.2.1", "electron-builder": "^24.6.4", "electron-updater": "^6.1.4", "eslint-config-standard": "^17.0.0", @@ -6555,9 +6555,9 @@ } }, "node_modules/electron": { - "version": "31.1.0", - "resolved": "https://registry.npmjs.org/electron/-/electron-31.1.0.tgz", - "integrity": "sha512-TBOwqLxSxnx6+pH6GMri7R3JPH2AkuGJHfWZS0p1HsmN+Qr1T9b0IRJnnehSd/3NZAmAre4ft9Ljec7zjyKFJA==", + "version": "31.2.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-31.2.1.tgz", + "integrity": "sha512-g3CLKjl4yuXt6VWm/KpgEjYYhFiCl19RgUn8lOC8zV/56ZXAS3+mqV4wWzicE/7vSYXs6GRO7vkYRwrwhX3Gaw==", "dev": true, "hasInstallScript": true, "dependencies": { diff --git a/package.json b/package.json index 393e6ffd..dd5cc714 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "babel-loader": "^9.1.0", "c8": "^10.1.2", "css-loader": "^7.1.2", - "electron": "^31.1.0", + "electron": "^31.2.1", "electron-builder": "^24.6.4", "electron-updater": "^6.1.4", "eslint-config-standard": "^17.0.0", From 0eb4e8d90aa299104b221005871f5f3400eab1c9 Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 16 Jul 2024 08:06:34 +0200 Subject: [PATCH 65/73] fixed issue with undefined geometry. --- src/renderer/model/sources/featureSource.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index 575dbb60..bca149e6 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -23,7 +23,7 @@ const readFeature = R.curry((state, source) => { // A word of caution: It is strongly adviced to NOT use feature signal // DIRECTLY to derive style! Setting the featues style will update the - // feature's revision and thus lead to a infinate loop. + // feature's revision and thus lead to an infinite loop. // Always make sure to extract relevant information from feature into // new signals which conversely are only updated when this information // has actually changed. @@ -37,8 +37,8 @@ const readFeature = R.curry((state, source) => { selectionMode: Signal.of('default') } - feature.$.styles = styles(feature) - feature.$.styles.on(feature.setStyle.bind(feature)) + const setStyle = feature.setStyle.bind(feature) + styles(feature).on(setStyle) // Use dedicated function to update feature coordinates from within // modify interaction. Such internal changes must not trigger ModifyEvent. @@ -117,6 +117,7 @@ export const featureSource = services => { const getFeatureById = source.getFeatureById.bind(source) const getFeaturesById = ids => ids.map(getFeatureById).filter(Boolean) + // Load styles and features. ;(async () => { state.styles = await store.dictionary('style+') const tuples = [ @@ -160,7 +161,10 @@ export const featureSource = services => { if (type === 'del') source.removeFeature(feature) else if (feature) { feature.setProperties(value.properties) - feature.setGeometry(format.readGeometry(value.geometry)) + // It is possible that only properties have changed. + // Don't set null/undefined geometry! + const geometry = format.readGeometry(value.geometry) + if (geometry) feature.setGeometry(geometry) } else { feature = readFeature(state, { id: key, ...value }) source.addFeature(feature) From 033230b52f230c986f3a41df603fa9fe33351b32 Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 16 Jul 2024 08:44:24 +0200 Subject: [PATCH 66/73] updated own packages. --- package-lock.json | 10 ++++++---- package.json | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2408fa53..d8e5e8e3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,8 @@ "dependencies": { "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.0", - "@syncpoint/signal": "github:syncpoint/signal", - "@syncpoint/signs": "github:syncpoint/signs", + "@syncpoint/signal": "^1.2.0", + "@syncpoint/signs": "^1.1.0", "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", "color": "^4.2.3", @@ -2938,11 +2938,13 @@ }, "node_modules/@syncpoint/signal": { "version": "1.2.0", - "resolved": "git+ssh://git@github.com/syncpoint/signal.git#c77316f880a38a9d1447b137984ae5ae2478967b" + "resolved": "https://registry.npmjs.org/@syncpoint/signal/-/signal-1.2.0.tgz", + "integrity": "sha512-h4pEjSOBdDzl2ICog5TlPpy++uLRXE3pgC9mfy6ivfH8gZRA4earsKIq7wNMBBamqHZGQ3RU59HC9oaravP40g==" }, "node_modules/@syncpoint/signs": { "version": "1.1.0", - "resolved": "git+ssh://git@github.com/syncpoint/signs.git#127ca0dcefe73ec3ebc8cf5ab0841eb622930ce6", + "resolved": "https://registry.npmjs.org/@syncpoint/signs/-/signs-1.1.0.tgz", + "integrity": "sha512-SOXi5y1znjQ77hjxrYEudy76K6CC1NNkzqqTu4gj1nFMu3qzAdsK5KD9xH5e8jEfaEO9YtVYVGnBlmfUhQOq2w==", "dependencies": { "ramda": "^0.30.1", "svg-path-bbox": "^2.0.0" diff --git a/package.json b/package.json index dd5cc714..889a1608 100644 --- a/package.json +++ b/package.json @@ -64,8 +64,8 @@ "dependencies": { "@mdi/js": "^7.0.96", "@mdi/react": "^1.6.0", - "@syncpoint/signal": "github:syncpoint/signal", - "@syncpoint/signs": "github:syncpoint/signs", + "@syncpoint/signal": "^1.2.0", + "@syncpoint/signs": "^1.1.0", "@syncpoint/wkx": "^0.5.2", "abstract-leveldown": "^7.2.0", "color": "^4.2.3", From ddae088ea6b4d5a007a6de8d14add1d2fbc0a730 Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 16 Jul 2024 16:15:25 +0200 Subject: [PATCH 67/73] https://github.com/advisories/GHSA-9jxc-qjr9-vjxq --- package-lock.json | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d8e5e8e3..9e28cfe2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6924,12 +6924,12 @@ "dev": true }, "node_modules/electron-updater": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.2.1.tgz", - "integrity": "sha512-83eKIPW14qwZqUUM6wdsIRwVKZyjmHxQ4/8G+1C6iS5PdDt7b1umYQyj1/qPpH510GmHEQe4q0kCPe3qmb3a0Q==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.3.0.tgz", + "integrity": "sha512-3Xlezhk+dKaSQrOnkQNqCGiuGSSUPO9BV9TQZ4Iig6AyTJ4FzJONE5gFFc382sY53Sh9dwJfzKsA3DxRHt2btw==", "dev": true, "dependencies": { - "builder-util-runtime": "9.2.4", + "builder-util-runtime": "9.2.5", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", @@ -6939,6 +6939,19 @@ "tiny-typed-emitter": "^2.1.0" } }, + "node_modules/electron-updater/node_modules/builder-util-runtime": { + "version": "9.2.5", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.5.tgz", + "integrity": "sha512-HjIDfhvqx/8B3TDN4GbABQcgpewTU4LMRTQPkVpKYV3lsuxEJoIfvg09GyWTNmfVNSUAYf+fbTN//JX4TH20pg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/electron-updater/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", From 6092a3b3a602e9ccd2a0cf0ce76c4d1cf26852c2 Mon Sep 17 00:00:00 2001 From: dehmer Date: Tue, 16 Jul 2024 16:18:10 +0200 Subject: [PATCH 68/73] chore: updated minisearch@7.0.1 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9e28cfe2..9ec519e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "leveldown": "^6.1.1", "levelup": "^5.0.1", "luxon": "^3.1.0", - "minisearch": "^7.0.0", + "minisearch": "^7.0.1", "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", @@ -11845,9 +11845,9 @@ "dev": true }, "node_modules/minisearch": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.0.0.tgz", - "integrity": "sha512-0OIJ3hUE+YBJNruDCqbTMFmk/IoB1CpZzuGfl11khFIel66ew9UoLF/+gfq3bdyrneqr3P7BTjFZApUbmk+9Dg==" + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.0.1.tgz", + "integrity": "sha512-xLeX/AwTJLzgBF2/bdUI7MEePwXtzaLExkRwu8YFGfLDwSe06KYkplqPodLANsqvfc5Ks/r5ItFUSjIp7+9xtw==" }, "node_modules/minizlib": { "version": "2.1.2", diff --git a/package.json b/package.json index 889a1608..0a895c2e 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "leveldown": "^6.1.1", "levelup": "^5.0.1", "luxon": "^3.1.0", - "minisearch": "^7.0.0", + "minisearch": "^7.0.1", "mousetrap": "^1.6.5", "mousetrap-global-bind": "^1.1.0", "ol": "^9.2.4", From 3f4661deabd750f8b1e92993b13217a6ad9735a6 Mon Sep 17 00:00:00 2001 From: dehmer Date: Wed, 17 Jul 2024 18:53:06 +0200 Subject: [PATCH 69/73] simplified geometry: clone when not simplified. --- src/renderer/ol/style/_simplifiedGeometry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/ol/style/_simplifiedGeometry.js b/src/renderer/ol/style/_simplifiedGeometry.js index 5eee0998..be4fb68b 100644 --- a/src/renderer/ol/style/_simplifiedGeometry.js +++ b/src/renderer/ol/style/_simplifiedGeometry.js @@ -11,5 +11,5 @@ export default (geometry, resolution) => { return simplify ? geometry.simplify(resolution) - : geometry + : geometry.clone() } From 4549d23c6d9eeb25ccbeaf754c42df26c1ff6c4a Mon Sep 17 00:00:00 2001 From: dehmer Date: Wed, 17 Jul 2024 19:08:31 +0200 Subject: [PATCH 70/73] rendering: provide current style instead of pushing updates. --- src/renderer/model/sources/featureSource.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/model/sources/featureSource.js b/src/renderer/model/sources/featureSource.js index bca149e6..94754eae 100644 --- a/src/renderer/model/sources/featureSource.js +++ b/src/renderer/model/sources/featureSource.js @@ -37,8 +37,8 @@ const readFeature = R.curry((state, source) => { selectionMode: Signal.of('default') } - const setStyle = feature.setStyle.bind(feature) - styles(feature).on(setStyle) + feature.$.style = styles(feature) + feature.setStyle(feature => feature.$.style()) // Use dedicated function to update feature coordinates from within // modify interaction. Such internal changes must not trigger ModifyEvent. From 03d7a37159fc753927725e7aef633c75db28c46d Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 18 Jul 2024 08:38:53 +0200 Subject: [PATCH 71/73] simplified geometry: improved efficiency. --- src/renderer/ol/style/_simplifiedGeometry.js | 2 +- src/renderer/ol/style/linestring.js | 3 ++- src/renderer/ol/style/polygon.js | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/renderer/ol/style/_simplifiedGeometry.js b/src/renderer/ol/style/_simplifiedGeometry.js index be4fb68b..5eee0998 100644 --- a/src/renderer/ol/style/_simplifiedGeometry.js +++ b/src/renderer/ol/style/_simplifiedGeometry.js @@ -11,5 +11,5 @@ export default (geometry, resolution) => { return simplify ? geometry.simplify(resolution) - : geometry.clone() + : geometry } diff --git a/src/renderer/ol/style/linestring.js b/src/renderer/ol/style/linestring.js index 20d661fa..a5018844 100644 --- a/src/renderer/ol/style/linestring.js +++ b/src/renderer/ol/style/linestring.js @@ -3,6 +3,7 @@ import labels from './linestring-styles/labels' import styles from './linestring-styles/index' import placement from './linestring-styles/placement' import graphics from './graphics' +import keyequals from './keyequals' import _smoothenedGeometry from './_smoothenedGeometry' import _simplifiedGeometry from './_simplifiedGeometry' @@ -13,7 +14,7 @@ import _lineSmoothing from './_lineSmoothing' import _selection from './_selection' const specifics = $ => { - $.simplifiedGeometry = Signal.link(_simplifiedGeometry, [$.geometry, $.centerResolution]) + $.simplifiedGeometry = Signal.link(_simplifiedGeometry, [$.geometry, $.centerResolution], { equals: keyequals() }) $.jtsSimplifiedGeometry = $.simplifiedGeometry.ap($.read) $.lineSmoothing = $.effectiveStyle.map(_lineSmoothing) $.smoothenedGeometry = Signal.link(_smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) diff --git a/src/renderer/ol/style/polygon.js b/src/renderer/ol/style/polygon.js index c32af3fe..3ac15426 100644 --- a/src/renderer/ol/style/polygon.js +++ b/src/renderer/ol/style/polygon.js @@ -3,6 +3,7 @@ import labels from './polygon-styles/labels' import styles from './polygon-styles/index' import placement from './polygon-styles/placement' import graphics from './graphics' +import keyequals from './keyequals' import _smoothenedGeometry from './_smoothenedGeometry' import _simplifiedGeometry from './_simplifiedGeometry' @@ -13,7 +14,7 @@ import _lineSmoothing from './_lineSmoothing' import _selection from './_selection' const specifics = $ => { - $.simplifiedGeometry = Signal.link(_simplifiedGeometry, [$.geometry, $.centerResolution]) + $.simplifiedGeometry = Signal.link(_simplifiedGeometry, [$.geometry, $.centerResolution], { equals: keyequals() }) $.jtsSimplifiedGeometry = $.simplifiedGeometry.ap($.read) $.lineSmoothing = $.effectiveStyle.map(_lineSmoothing) $.smoothenedGeometry = Signal.link(_smoothenedGeometry, [$.simplifiedGeometry, $.lineSmoothing]) From f6c2cdf9b1799b7812bc6dcd3b9f9b4ca71bdbc5 Mon Sep 17 00:00:00 2001 From: dehmer Date: Thu, 18 Jul 2024 17:28:36 +0200 Subject: [PATCH 72/73] modify-interaction: fixed regression. --- src/renderer/ol/interaction/modify/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderer/ol/interaction/modify/index.js b/src/renderer/ol/interaction/modify/index.js index 39e840a3..f3b7cbea 100644 --- a/src/renderer/ol/interaction/modify/index.js +++ b/src/renderer/ol/interaction/modify/index.js @@ -10,6 +10,7 @@ import * as Events from './events' import { selected } from './states' import { ModifyEvent } from './events' import { writeIndex } from './writers' +import { flat } from '../../../../shared/signal' /** * Sink for vertex feature overlay. @@ -114,12 +115,13 @@ export class Modify extends Interaction { if (rbush.isEmpty()) return [selected(true), Events.coordinate(null)] const pointer = Events.pointer(options, rbush, event) const handler = state[event.type] - return (handler && handler(pointer)) || [state, null] + return (handler && handler(pointer)) || [state, undefined] } // stateLoop :: Signal { type: 'coordinate, ... } | ModifyEvent const stateLoop = R.compose( R.reject(R.isNil), + flat, Signal.loop(eventHandler, selected(true)) )(spatialEvent) From 6d5508b6909d38a0b4e69802fc6dc3365fb9ab39 Mon Sep 17 00:00:00 2001 From: dehmer Date: Sun, 21 Jul 2024 13:12:00 +0200 Subject: [PATCH 73/73] corridor: fixed usage of error style. --- src/renderer/ol/style/_shape.js | 2 +- src/renderer/ol/style/linestring-styles/index.js | 6 +++++- src/renderer/ol/style/multipoint-styles/index.js | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/renderer/ol/style/_shape.js b/src/renderer/ol/style/_shape.js index c9644d49..ae87976e 100644 --- a/src/renderer/ol/style/_shape.js +++ b/src/renderer/ol/style/_shape.js @@ -5,6 +5,6 @@ import * as R from 'ramda' */ export default styles => sidc => { const tryer = (styles[sidc] || styles.DEFAULT) - const catcher = (_, context) => [{ id: 'style:wasp-stroke', geometry: context.geometry }] + const catcher = (_, context) => (styles.ERROR || styles.DEFAULT)(context) return R.tryCatch(tryer, catcher) } diff --git a/src/renderer/ol/style/linestring-styles/index.js b/src/renderer/ol/style/linestring-styles/index.js index 0b9d5e6d..58511160 100644 --- a/src/renderer/ol/style/linestring-styles/index.js +++ b/src/renderer/ol/style/linestring-styles/index.js @@ -36,8 +36,12 @@ import G_T_A from './G_T_A' import G_T_AS from './G_T_AS' import G_T_F from './G_T_F' +const DEFAULT = ({ geometry }) => [{ id: 'style:2525c/default-stroke', geometry }] +const ERROR = ({ geometry }) => [{ id: 'style:wasp-stroke', geometry }] + export default { - DEFAULT: ({ geometry }) => [{ id: 'style:2525c/default-stroke', geometry }], + DEFAULT, + ERROR, 'G*F*LT----': G_F_LT, // LINEAR TARGET 'G*F*LTF---': G_F_LT, // FINAL PROTECTIVE FIRE (FPF) 'G*F*LTS---': G_F_LT, // LINEAR SMOKE TARGET diff --git a/src/renderer/ol/style/multipoint-styles/index.js b/src/renderer/ol/style/multipoint-styles/index.js index ba6183da..1558ce4b 100644 --- a/src/renderer/ol/style/multipoint-styles/index.js +++ b/src/renderer/ol/style/multipoint-styles/index.js @@ -17,11 +17,12 @@ const circle = id => ({ TS, geometry }) => { return [{ id, geometry: buffer }] } +const DEFAULT = ({ geometry }) => [{ id: 'style:2525c/default-stroke', geometry }] const CIRCLE = circle('style:2525c/default-stroke') const FILLED_CIRCLE = circle('style:2525c/hatch-fill') export default { - DEFAULT: ({ geometry }) => [{ id: 'style:2525c/default-stroke', geometry }], + DEFAULT, CIRCLE, FILLED_CIRCLE, 'G*F*AXC---': G_F_AXC, // SENSOR RANGE FAN