Skip to content

Commit

Permalink
feat: avoid mass creation and deletion of geometries in leaflet adapt…
Browse files Browse the repository at this point in the history
…er render
  • Loading branch information
JamesLMilner committed Apr 10, 2023
1 parent 4f08298 commit 773208a
Showing 1 changed file with 108 additions and 102 deletions.
210 changes: 108 additions & 102 deletions src/adapters/leaflet.adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ export class TerraDrawLeafletAdapter extends TerraDrawBaseAdapter {

private _lib: typeof L;
private _map: L.Map;
private _layer: L.Layer | undefined;
private _panes: Record<string, HTMLStyleElement | undefined> = {};
private _container: HTMLElement;
private _layers: Record<string, L.GeoJSON<any>> = {};

/**
* Creates a pane and its associated style sheet
Expand All @@ -42,6 +42,96 @@ export class TerraDrawLeafletAdapter extends TerraDrawBaseAdapter {
return style;
}

private styleGeoJSONLayer(
styling: TerraDrawStylingFunction
): L.GeoJSONOptions {
return {
// Style points - convert markers to circle markers
pointToLayer: (
feature: GeoJSONStoreFeatures,
latlng: L.LatLngExpression
) => {
if (!feature.properties) {
throw new Error("Feature has no properties");
}
if (typeof feature.properties.mode !== "string") {
throw new Error("Feature mode is not a string");
}

const mode = feature.properties.mode;
const modeStyle = styling[mode];
const featureStyles = modeStyle(feature);
const paneId = String(featureStyles.zIndex);
const pane = this._panes[paneId];

if (!pane) {
this._panes[paneId] = this.createPaneStyleSheet(
paneId,
featureStyles.zIndex
);
}

const styles = {
radius: featureStyles.pointWidth,
stroke: featureStyles.pointOutlineWidth || false,
color: featureStyles.pointOutlineColor,
weight: featureStyles.pointOutlineWidth,
fillOpacity: 0.8,
fillColor: featureStyles.pointColor,
pane: paneId,
interactive: false, // Removes mouse hover cursor styles
} as L.CircleMarkerOptions;

const marker = this._lib.circleMarker(latlng, styles);

return marker;
},

// Style LineStrings and Polygons
style: (_feature) => {
if (!_feature || !_feature.properties) {
return {};
}

const feature = _feature as GeoJSONStoreFeatures;

const mode = feature.properties.mode as string;
const modeStyle = styling[mode];
const featureStyles = modeStyle(feature);
const paneId = String(featureStyles.zIndex);
const pane = this._panes[paneId];

if (!pane) {
this._panes[paneId] = this.createPaneStyleSheet(
paneId,
featureStyles.zIndex
);
}

if (feature.geometry.type === "LineString") {
return {
interactive: false, // Removes mouse hover cursor styles
color: featureStyles.lineStringColor,
weight: featureStyles.lineStringWidth,
pane: paneId,
};
} else if (feature.geometry.type === "Polygon") {
return {
interactive: false, // Removes mouse hover cursor styles
fillOpacity: featureStyles.polygonFillOpacity,
fillColor: featureStyles.polygonFillColor,
weight: featureStyles.polygonOutlineWidth,
stroke: true,
color: featureStyles.polygonFillColor,
pane: paneId,
};
}

return {};
},
};
}

/**
* Returns the longitude and latitude coordinates from a given PointerEvent on the map.
* @param event The PointerEvent or MouseEvent containing the screen coordinates of the pointer.
Expand Down Expand Up @@ -143,109 +233,25 @@ export class TerraDrawLeafletAdapter extends TerraDrawBaseAdapter {
* @param styling An object mapping draw modes to feature styling functions
*/
public render(changes: TerraDrawChanges, styling: TerraDrawStylingFunction) {
const features = [
...changes.created,
...changes.updated,
...changes.unchanged,
];

if (this._layer) {
this._map.removeLayer(this._layer);
}

const featureCollection = {
type: "FeatureCollection",
features,
} as { type: "FeatureCollection"; features: GeoJSONStoreFeatures[] };

const layer = this._lib.geoJSON(featureCollection, {
// Style points - convert markers to circle markers
pointToLayer: (
feature: GeoJSONStoreFeatures,
latlng: L.LatLngExpression
) => {
if (!feature.properties) {
throw new Error("Feature has no properties");
}
if (typeof feature.properties.mode !== "string") {
throw new Error("Feature mode is not a string");
}

const mode = feature.properties.mode;
const modeStyle = styling[mode];
const featureStyles = modeStyle(feature);
const paneId = String(featureStyles.zIndex);
const pane = this._panes[paneId];

if (!pane) {
this._panes[paneId] = this.createPaneStyleSheet(
paneId,
featureStyles.zIndex
);
}

const styles = {
radius: featureStyles.pointWidth,
stroke: featureStyles.pointOutlineWidth || false,
color: featureStyles.pointOutlineColor,
weight: featureStyles.pointOutlineWidth,
fillOpacity: 0.8,
fillColor: featureStyles.pointColor,
pane: paneId,
interactive: false, // Removes mouse hover cursor styles
} as L.CircleMarkerOptions;

const marker = this._lib.circleMarker(latlng, styles);

return marker;
},

// Style LineStrings and Polygons
style: (_feature) => {
if (!_feature || !_feature.properties) {
return {};
}

const feature = _feature as GeoJSONStoreFeatures;

const mode = feature.properties.mode as string;
const modeStyle = styling[mode];
const featureStyles = modeStyle(feature);
const paneId = String(featureStyles.zIndex);
const pane = this._panes[paneId];

if (!pane) {
this._panes[paneId] = this.createPaneStyleSheet(
paneId,
featureStyles.zIndex
);
}

if (feature.geometry.type === "LineString") {
return {
interactive: false, // Removes mouse hover cursor styles
color: featureStyles.lineStringColor,
weight: featureStyles.lineStringWidth,
pane: paneId,
};
} else if (feature.geometry.type === "Polygon") {
return {
interactive: false, // Removes mouse hover cursor styles
fillOpacity: featureStyles.polygonFillOpacity,
fillColor: featureStyles.polygonFillColor,
weight: featureStyles.polygonOutlineWidth,
stroke: true,
color: featureStyles.polygonFillColor,
pane: paneId,
};
}

return {};
},
changes.created.forEach((created) => {
this._layers[created.id as string] = this._lib.geoJSON(
created,
this.styleGeoJSONLayer(styling)
);
this._map.addLayer(this._layers[created.id as string]);
});

this._map.addLayer(layer);
changes.deletedIds.forEach((deleted) => {
this._map.removeLayer(this._layers[deleted]);
});

this._layer = layer;
changes.updated.forEach((updated) => {
this._map.removeLayer(this._layers[updated.id as string]);
this._layers[updated.id as string] = this._lib.geoJSON(
updated,
this.styleGeoJSONLayer(styling)
);
this._map.addLayer(this._layers[updated.id as string]);
});
}
}

0 comments on commit 773208a

Please sign in to comment.