Skip to content
Closed
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
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/>
</head>
<body>
<my-map zoom="18" maxZoom="20" drawMode />
<my-map zoom="18" maxZoom="23" drawMode />

<script>
const map = document.querySelector("my-map");
Expand Down
19 changes: 13 additions & 6 deletions src/draw.ts → src/drawing.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import MultiPoint from 'ol/geom/MultiPoint';
import MultiPoint from "ol/geom/MultiPoint";
import { Draw, Modify, Snap } from "ol/interaction";
import { Vector as VectorLayer } from "ol/layer";
import { Vector as VectorSource } from "ol/source";
import { Circle as CircleStyle, Fill, RegularShape, Stroke, Style } from "ol/style";
import {
Circle as CircleStyle,
Fill,
RegularShape,
Stroke,
Style,
} from "ol/style";
import { pointsSource } from "./snapping";

const redLineBase = {
color: '#ff0000',
color: "#ff0000",
width: 3,
};

Expand All @@ -30,7 +37,7 @@ const drawingPointer = new CircleStyle({
const drawingVertices = new Style({
image: new RegularShape({
fill: new Fill({
color: "#fff"
color: "#fff",
}),
stroke: new Stroke({
color: "#ff0000",
Expand All @@ -57,7 +64,7 @@ export const drawingLayer = new VectorLayer({
stroke: redLineStroke,
}),
drawingVertices,
]
],
});

export const draw = new Draw({
Expand All @@ -71,7 +78,7 @@ export const draw = new Draw({
});

export const snap = new Snap({
source: drawingSource,
source: pointsSource, // empty if OS VectorTile basemap is disabled & zoom > 20
pixelTolerance: 15,
});

Expand Down
62 changes: 48 additions & 14 deletions src/my-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,19 @@ import { Fill, Stroke, Style } from "ol/style";
import View from "ol/View";
import { last } from "rambda";

import { draw, drawingLayer, drawingSource, modify, snap } from "./draw";
import { scaleControl } from "./scale-line";
import { draw, drawingLayer, drawingSource, modify, snap } from "./drawing";
import {
getFeaturesAtPoint,
makeFeatureLayer,
outlineSource,
getFeaturesAtPoint,
} from "./os-features";
import { makeOsVectorTileBaseMap, makeRasterBaseMap } from "./os-layers";
import { scaleControl } from "./scale-line";
import {
getSnapPointsFromVectorTiles,
pointsLayer,
pointsSource,
} from "./snapping";
import { AreaUnitEnum, fitToData, formatArea, hexToRgba } from "./utils";

@customElement("my-map")
Expand Down Expand Up @@ -221,7 +226,7 @@ export class MyMap extends LitElement {
map.getViewport().style.cursor = "grab";
});

// add a vector layer to display static geojson if features are provided
// display static geojson if features are provided
const geojsonSource = new VectorSource();

if (this.geojsonData.type === "FeatureCollection") {
Expand Down Expand Up @@ -259,15 +264,14 @@ export class MyMap extends LitElement {

// log total area of static geojson data (assumes single polygon for now)
const data = geojsonSource.getFeatures()[0].getGeometry();
this.dispatch(
"geojsonDataArea",
formatArea(data, this.areaUnit)
);
this.dispatch("geojsonDataArea", formatArea(data, this.areaUnit));
}

// draw interactions
if (this.drawMode) {
// check if single polygon feature was provided to load as the initial drawing
const loadInitialDrawing = Object.keys(this.drawGeojsonData.geometry).length > 0;
const loadInitialDrawing =
Object.keys(this.drawGeojsonData.geometry).length > 0;
if (loadInitialDrawing) {
let feature = new GeoJSON().readFeature(this.drawGeojsonData, {
featureProjection: "EPSG:3857",
Expand Down Expand Up @@ -313,6 +317,31 @@ export class MyMap extends LitElement {
});
}

// show snapping points when in drawMode, with vector tile basemap enabled, and at zoom > 20
if (
this.drawMode &&
Boolean(this.osVectorTilesApiKey) &&
!this.disableVectorTiles
) {
map.addLayer(pointsLayer);
drawingLayer.setZIndex(1001); // display draw vertices on top of snap points

map.on("moveend", () => {
if (map.getView().getZoom() < 20) {
pointsSource.clear();
return;
}

// extract snap-able points from the basemap, and display them as points on the map
setTimeout(() => {
pointsSource.clear();
const extent = map.getView().calculateExtent(map.getSize());
getSnapPointsFromVectorTiles(osVectorTileBaseMap, extent);
}, 200);
});
}

// OS Features API & click-to-select interactions
if (this.showFeaturesAtPoint && Boolean(this.osFeaturesApiKey)) {
getFeaturesAtPoint(
fromLonLat([this.longitude, this.latitude]),
Expand All @@ -339,7 +368,7 @@ export class MyMap extends LitElement {
) {
// fit map to extent of features
fitToData(map, outlineSource, this.featureBuffer);

// write the geojson representation of the feature or merged features
this.dispatch(
"featuresGeojsonChange",
Expand All @@ -348,12 +377,17 @@ export class MyMap extends LitElement {
})
);

// calculate the total area of the feature or merged features
const data = outlineSource.getFeatures()[0].getGeometry();
// write the geojson representation of the feature or merged features
this.dispatch(
"featuresAreaChange",
formatArea(data, this.areaUnit)
"featuresGeojsonChange",
new GeoJSON().writeFeaturesObject(outlineSource.getFeatures(), {
featureProjection: "EPSG:3857",
})
);

// calculate the total area of the feature or merged features
const data = outlineSource.getFeatures()[0].getGeometry();
this.dispatch("featuresAreaChange", formatArea(data, this.areaUnit));
}
});
}
Expand Down
51 changes: 51 additions & 0 deletions src/snapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Feature } from "ol";
import Point from "ol/geom/Point";
import { Vector as VectorLayer } from "ol/layer";
import VectorTileLayer from "ol/layer/VectorTile";
import VectorSource from "ol/source/Vector";
import { Fill, Style } from "ol/style";
import CircleStyle from "ol/style/Circle";
import { splitEvery } from "rambda";

export const pointsSource = new VectorSource({
features: [],
wrapX: false,
});

export const pointsLayer = new VectorLayer({
source: pointsSource,
style: new Style({
image: new CircleStyle({
radius: 3,
fill: new Fill({
color: "black",
}),
}),
}),
});

/**
* Extract points that are available to snap to when a VectorTileLayer basemap is displayed
* @param basemap - a VectorTileLayer
* @param extent - an array of 4 points
* @returns - a VectorSource populated with points within the extent
*/
export function getSnapPointsFromVectorTiles(
basemap: VectorTileLayer,
extent: number[]
) {
const points = basemap
.getSource()
.getFeaturesInExtent(extent)
.filter((feature) => feature.getGeometry().getType() !== "Point")
.flatMap((feature: any) => feature.flatCoordinates_);

return (splitEvery(2, points) as [number, number][]).forEach((pair, i) => {
pointsSource.addFeature(
new Feature({
geometry: new Point(pair),
i,
})
);
});
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"emitDeclarationOnly": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"lib": ["es2017", "dom", "dom.iterable"],
"lib": ["es2019", "dom", "dom.iterable"],
"module": "es2020",
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
Expand Down