diff --git a/ohmg/frontend/svelte_components/src/components/Georeferencer.svelte b/ohmg/frontend/svelte_components/src/components/Georeferencer.svelte
index 83106e45..b63bf8db 100644
--- a/ohmg/frontend/svelte_components/src/components/Georeferencer.svelte
+++ b/ohmg/frontend/svelte_components/src/components/Georeferencer.svelte
@@ -23,11 +23,9 @@
import { transformExtent } from 'ol/proj';
import { containsXY } from 'ol/extent';
- import { inflateCoordinatesArray } from 'ol/geom/flat/inflate';
import Draw from 'ol/interaction/Draw';
import Modify from 'ol/interaction/Modify';
- import Snap from 'ol/interaction/Snap';
import { gcpStyles, parcelStyles, emptyStyle } from '../lib/ol-styles';
import {
@@ -93,8 +91,6 @@
let currentBasemap;
let currentZoom;
- let enableSnapLayer = false;
-
let currentPreviewId;
let defaultExtent;
@@ -201,7 +197,7 @@
const mapGCPLayer = new VectorLayer({
source: mapGCPSource,
style: gcpStyles.default,
- zIndex: 30,
+ zIndex: 100,
});
// CREATE DISPLAY LAYERS
@@ -302,28 +298,9 @@
}
// SNAP LAYER STUFF
- let pmLayer;
+ let parcelLayer;
let parcelEntry;
let localeMatch;
- MAP.locale_lineage.forEach((slug) => {
- if (parcelLookup[slug]) {
- localeMatch = slug;
- parcelEntry = parcelLookup[slug];
- pmLayer = makePmTilesLayer(
- parcelEntry.pmtilesUrl,
- `${parcelEntry.attributionText}`,
- parcelStyles.inactive,
- );
- return;
- }
- });
- const snapSource = new VectorSource({
- overlaps: false,
- });
- const snapLayer = new VectorLayer({
- source: snapSource,
- style: emptyStyle,
- });
// MAKING INTERACTIONS
@@ -397,6 +374,7 @@
return containsXY(docExtent, mapBrowserEvent.coordinate[0], mapBrowserEvent.coordinate[1]);
}
+
docViewer.addInteraction('draw', makeDrawInteraction(docGCPSource, drawWithinDocCondition, emptyStyle));
docViewer.addInteraction('modify', makeModifyInteraction(docGCPSource, docViewer.element));
@@ -420,8 +398,10 @@
mapViewer.addControl(new MapScaleLine());
// create interactions
- const mapDrawGCPStyle = pmLayer ? gcpStyles.snapTarget : emptyStyle;
- mapViewer.addInteraction('draw', makeDrawInteraction(mapGCPSource, null, mapDrawGCPStyle));
+ function drawStyleFunction() {
+ return parcelLayer.getVisible() ? gcpStyles.snapTarget : emptyStyle
+ }
+ mapViewer.addInteraction('draw', makeDrawInteraction(mapGCPSource, null, drawStyleFunction));
mapViewer.addInteraction('modify', makeModifyInteraction(mapGCPSource, mapViewer.element));
// add some event listening to the map
@@ -438,17 +418,20 @@
mapViewer.addLayer(mainLayerGroup);
mapViewer.addLayer(mainLayerGroup50);
- // snap to parcels --- work-in-progress!
- const snap = new Snap({
- source: snapSource,
- edge: false,
+ MAP.locale_lineage.forEach((slug) => {
+ if (parcelLookup[slug]) {
+ localeMatch = slug
+ parcelLayer = makePmTilesLayer(
+ parcelLookup[slug].pmtilesUrl,
+ `${parcelLookup[slug].attributionText}`,
+ parcelStyles.inactive,
+ );
+ parcelLayer.setZIndex(30)
+ parcelLayer.setVisible(false)
+ mapViewer.addSnappableVectorLayer(parcelLayer, 10, 17, parcelStyles.active, parcelStyles.inactive)
+ return;
+ }
});
- mapViewer.addInteraction('parcelSnap', snap);
- mapViewer.interactions.parcelSnap.setActive(false);
- // tried map.on('rendercomplete') here but sometimes it would fire constantly,
- // so using these more specific event listeners
- mapViewer.map.getView().on('change:resolution', refreshSnapSource);
- mapViewer.map.on('moveend', refreshSnapSource);
currentZoom = mapViewer.getZoom();
mapViewer.map.getView().on('change:resolution', () => {
@@ -620,70 +603,6 @@
}
}
- $: {
- if (currentZoom < 17) {
- enableParcelSnapping = false;
- }
- }
-
- $: enableParcelSnapping = currentZoom >= 17;
-
- function refreshSnapSource() {
- if (!enableParcelSnapping || !pmLayer) {
- return;
- }
- snapSource.clear();
- const features = pmLayer.getFeaturesInExtent(mapViewer.map.getView().calculateExtent());
- features.forEach(function (feature) {
- const lineCoords = inflateCoordinatesArray(
- feature.getFlatCoordinates(), // flat coordinates
- 0, // offset
- feature.getEnds(), // geometry end indices
- 2, // stride
- );
- const geoJsonGeom = { coordinates: lineCoords, type: 'Polygon' };
- const f = new GeoJSON().readFeature(geoJsonGeom, {
- dataProjection: 'EPSG:3857',
- });
- if (!snapSource.hasFeature(f)) {
- snapSource.addFeature(f);
- }
- });
- }
- function toggleSnap(enabled) {
- if (!mapViewer || !pmLayer) {
- return;
- }
- if (enabled) {
- mapViewer.interactions.parcelSnap.setActive(true);
- mapViewer.map.once('rendercomplete', refreshSnapSource);
- pmLayer.setStyle(parcelStyles.active);
- } else {
- snapSource.clear();
- mapViewer.interactions.parcelSnap.setActive(false);
- pmLayer.setStyle(parcelStyles.inactive);
- }
- }
- $: toggleSnap(enableParcelSnapping);
-
- function toggleParcelLayer(enabled) {
- if (!mapViewer || !pmLayer) {
- return;
- }
- if (currentZoom < 10) {
- enableParcelSnapping = false;
- }
- if (enabled) {
- mapViewer.addLayer(snapLayer);
- mapViewer.addLayer(pmLayer);
- } else {
- snapSource.clear();
- mapViewer.map.removeLayer(snapLayer);
- mapViewer.map.removeLayer(pmLayer);
- }
- }
- $: toggleParcelLayer(enableSnapLayer);
-
function setPreviewVisibility(mode) {
if (!mapViewer) {
return;
@@ -1077,11 +996,11 @@
{/each}
- {#if pmLayer}
+ {#if parcelLayer}
diff --git a/ohmg/frontend/svelte_components/src/lib/ol-styles.js b/ohmg/frontend/svelte_components/src/lib/ol-styles.js
index c6d6b654..6329353d 100644
--- a/ohmg/frontend/svelte_components/src/lib/ol-styles.js
+++ b/ohmg/frontend/svelte_components/src/lib/ol-styles.js
@@ -105,7 +105,12 @@ export const gcpStyles = {
default: [gcpOutline, makeGcpX(gcpColors.default)],
hover: [gcpOutline, makeGcpX(gcpColors.hover)],
selected: [gcpOutline, makeGcpX(gcpColors.selected)],
- snapTarget: smallCross,
+ snapTarget: new Style({
+ image: new Circle({
+ fill: new Fill({ color: colors.black }),
+ radius: 4,
+ }),
+ })
};
export const parcelStyles = {
@@ -123,6 +128,13 @@ export const parcelStyles = {
}),
};
+export const snapVertexStyle = new Style({
+ image: new Circle({
+ stroke: new Stroke({ color: colors.black, width: 1 }),
+ radius: 4,
+ }),
+});
+
export const emptyStyle = new Style();
// SPLIT INTERFACE
diff --git a/ohmg/frontend/svelte_components/src/lib/viewers.js b/ohmg/frontend/svelte_components/src/lib/viewers.js
index 4d57b6c2..161f0848 100644
--- a/ohmg/frontend/svelte_components/src/lib/viewers.js
+++ b/ohmg/frontend/svelte_components/src/lib/viewers.js
@@ -1,13 +1,24 @@
import Map from 'ol/Map';
import ZoomToExtent from 'ol/control/ZoomToExtent';
-import Draw from 'ol/interaction/Draw';
+import Snap from 'ol/interaction/Snap';
import Link from 'ol/interaction/Link.js';
+import VectorSource from 'ol/source/Vector';
+import VectorLayer from 'ol/layer/Vector';
+import GeoJSON from 'ol/format/GeoJSON';
+
+import { toGeometry } from 'ol/render/Feature';
+
+import { containsXY, intersects } from 'ol/extent';
+
+import { snapVertexStyle } from '../lib/ol-styles';
import { makeBasemaps } from './utils';
export class MapViewer {
interactions = {};
currentBasemap = null;
+ currentVectorTileExtents = [];
+ snapCandidates = []
constructor(elementId, maxTilesLoading) {
if (!maxTilesLoading) {
@@ -109,4 +120,130 @@ export class MapViewer {
getZoom() {
return Math.round(this.map.getView().getZoom() * 10) / 10;
}
+
+ addSnappableVectorLayer(layer, visMinZoom, snapMinZoom, activeStyle, inactiveStyle) {
+
+ const snapSource = new VectorSource({
+ overlaps: false,
+ });
+ const snapLayer = new VectorLayer({
+ source: snapSource,
+ zIndex: layer.get("zIndex") + 1,
+ style: snapVertexStyle,
+ });
+
+ const snap = new Snap({
+ source: snapSource,
+ });
+ this.addInteraction('parcelSnap', snap);
+
+ const collapseNestedCoordinates = (array, outCoords) => {
+ if (!outCoords) { outCoords = [] }
+ // if the first item in the list is not an array, then this is a coord
+ if (!Array.isArray(array[0])) {
+ outCoords.push(array)
+ } else {
+ array.forEach((a) => collapseNestedCoordinates(a, outCoords))
+ }
+ return outCoords
+ }
+
+ // this.map.on("moveend", () => {
+ // getTilesInCurrentView()
+ // })
+
+ const self = this;
+ function getTilesInCurrentView() {
+ const zoom = Math.floor(self.getZoom()); // Get current integer zoom level
+ const tileGrid = layer.getSource().getTileGrid(); // Get the tile grid from the source
+ // console.log(extent)
+ // console.log(tileGrid)
+ const tiles = [];
+
+ const [minX, minY, maxX, maxY] = self.map.getView().calculateExtent(self.map.getSize())
+ const cornerCoords = [[minX, minY], [maxX, minY], [minX, maxY], [maxX, maxY]]
+ cornerCoords.forEach((coord) => {
+ const tc = tileGrid.getTileCoordForCoordAndZ([coord[0], coord[1]], zoom)
+ console.log(tc)
+ const tile = layer.getSource().getTile(tc[0], tc[1], tc[2], 1, layer.getSource().getProjection())
+
+ });
+ // const blTc = tileGrid.getTileCoordForCoordAndZ([extent[0], extent[1]], zoom)
+ // const tlTc = tileGrid.getTileCoordForCoordAndZ([extent[0], extent[1]], zoom)
+
+ // const tileCoord = tileGrid.getTileCoordForCoordAndZ([extent[0], extent[1]], zoom)
+ // console.log(tile)
+
+
+ console.log(`Tiles intersecting view at zoom ${zoom}:`, tiles);
+ return tiles;
+ }
+
+ layer.getSource().on("tileloadend", (evt) => {
+ evt.tile.getFeatures().forEach((f) => {
+ const featCoords = collapseNestedCoordinates(toGeometry(f).getCoordinates());
+ const featCoordsInTileExtent = featCoords.filter(i => containsXY(evt.tile.extent, i[0], i[1]))
+ self.snapCandidates.push(...featCoordsInTileExtent)
+ });
+ })
+
+ const refreshSnapSource = () => {
+ snapSource.clear();
+ if (this.getZoom() >= snapMinZoom && layer.getVisible()) {
+
+ const currentExtent = this.map.getView().calculateExtent()
+
+ const usedCoords = []
+ this.snapCandidates.forEach((coord) => {
+ const coordRnd = [parseFloat(coord[0].toFixed(6)), parseFloat(coord[1].toFixed(6))]
+ const coordStr = coordRnd.toString()
+ if (!usedCoords.includes(coordStr)) {
+ if (containsXY(currentExtent, coord[0], coord[1])) {
+ const geoJsonGeom = { coordinates: coordRnd, type: 'Point' };
+ const pnt = new GeoJSON().readFeature(geoJsonGeom, {
+ dataProjection: 'EPSG:3857',
+ });
+ snapSource.addFeature(pnt);
+ usedCoords.push(coordStr)
+ }
+ }
+ })
+ }
+ }
+
+ this.map.on('moveend', () => {
+ refreshSnapSource()
+ });
+
+ layer.on('change:visible', () => {
+ if (layer.getVisible()) {
+ layer.once('postrender', () => {
+ setTimeout(refreshSnapSource, 500)
+ })
+ } else {
+ snapSource.clear()
+ }
+ })
+
+ this.map.getView().on('change:resolution', () => {
+ const currentZoom = this.getZoom();
+ // first handle the presence of this layer at all
+ if (currentZoom < visMinZoom) {
+ this.map.removeLayer(layer)
+ } else {
+ if (!this.map.getLayers().getArray().includes(layer)) {
+ this.map.addLayer(layer)
+ }
+ }
+ // now handle style based on whether it is snappable or not
+ if (currentZoom < snapMinZoom) {
+ layer.setStyle(inactiveStyle)
+ } else {
+ layer.setStyle(activeStyle)
+ if (!this.map.getLayers().getArray().includes(snapLayer)) {
+ this.map.addLayer(snapLayer)
+ }
+ }
+ });
+ }
}