Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 22 additions & 103 deletions ohmg/frontend/svelte_components/src/components/Georeferencer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -93,8 +91,6 @@
let currentBasemap;
let currentZoom;

let enableSnapLayer = false;

let currentPreviewId;

let defaultExtent;
Expand Down Expand Up @@ -201,7 +197,7 @@
const mapGCPLayer = new VectorLayer({
source: mapGCPSource,
style: gcpStyles.default,
zIndex: 30,
zIndex: 100,
});

// CREATE DISPLAY LAYERS
Expand Down Expand Up @@ -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,
`<a target="_blank" href="${parcelEntry.attributionUrl}">${parcelEntry.attributionText}</a>`,
parcelStyles.inactive,
);
return;
}
});
const snapSource = new VectorSource({
overlaps: false,
});
const snapLayer = new VectorLayer({
source: snapSource,
style: emptyStyle,
});

// MAKING INTERACTIONS

Expand Down Expand Up @@ -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));

Expand All @@ -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
Expand All @@ -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,
`<a target="_blank" href="${parcelLookup[slug].attributionUrl}">${parcelLookup[slug].attributionText}</a>`,
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', () => {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1077,11 +996,11 @@
{/each}
</select>
</label>
{#if pmLayer}
{#if parcelLayer}
<div style="display:flex; align-items:end;">
<label class="checkbox">
Parcels
<input type="checkbox" bind:checked={enableSnapLayer} disabled={currentZoom <= 10} />
<input type="checkbox" on:click={(evt) => {parcelLayer.setVisible(evt.target.checked)}} />
</label>
<InfoModalButton modalId="modal-parcels" size=".75em" />
</div>
Expand Down
14 changes: 13 additions & 1 deletion ohmg/frontend/svelte_components/src/lib/ol-styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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
Expand Down
139 changes: 138 additions & 1 deletion ohmg/frontend/svelte_components/src/lib/viewers.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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)
}
}
});
}
}