Skip to content
Merged
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
29 changes: 19 additions & 10 deletions src/draw.ts → src/drawing.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
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 {
Fill,
RegularShape,
Stroke,
Style,
} from "ol/style";
import { pointsSource } from "./snapping";

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

Expand All @@ -20,17 +26,20 @@ const redLineFill = new Fill({
color: "rgba(255, 0, 0, 0.1)",
});

const drawingPointer = new CircleStyle({
radius: 6,
fill: new Fill({
color: "#ff0000",
const drawingPointer = new RegularShape({
stroke: new Stroke({
color: 'red',
width: 2,
}),
points: 4, // crosshair aka star
radius1: 15, // outer radius
radius2: 1, // inner radius
});

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

export const draw = new Draw({
Expand All @@ -71,7 +80,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
54 changes: 40 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(() => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this timeout really necessay?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the timeout helps keep the zoom & pan interactions feeling a lot smoother in my opinion, and a bit more performant to only extract the snap points once a user has settled on location rather than mid-drag

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 @@ -350,10 +379,7 @@ export class MyMap extends LitElement {

// calculate the total area of the feature or merged features
const data = outlineSource.getFeatures()[0].getGeometry();
this.dispatch(
"featuresAreaChange",
formatArea(data, this.areaUnit)
);
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"],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep es2017 just in case?

"module": "es2020",
"moduleResolution": "node",
"noFallthroughCasesInSwitch": true,
Expand Down