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
13 changes: 13 additions & 0 deletions api/react-for-here.api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,13 @@ declare class HereCircleController extends CircleController<HereActualCircle> {
*/

declare class HereGroundImageOverlayRenderer extends AbstractGroundImageOverlayRenderer<HereViewHolder, HereActualGroundImage> {
private provider;
/**
* Ground images live in an ObjectLayer of their own, in map.getLayers() above the rasters.
* In the map's own object layer an `H.map.Overlay` is drawn over the polygons, circles and
* polylines whatever its zIndex (it was given -100,000,000 and still covered them).
*/
private objects;
createGroundImage(state: GroundImageState): Promise<HereActualGroundImage | null>;
updateGroundImageProperties({ groundImage, current, prev, }: {
groundImage: HereActualGroundImage;
Expand Down Expand Up @@ -416,6 +423,12 @@ declare class HereRasterLayerOverlayRenderer implements RasterLayerOverlayRender
onPostProcess(): Promise<void>;
private addLayer;
private updateLayer;
/**
* Under the ground-image layer (rasters are the lowest band), ordered by zIndex. Marker tiles
* go above it; they still cannot reach above the polygons, which HERE draws in its object
* layer over every layer.
*/
private addToMap;
private removeLayer;
}

Expand Down
5 changes: 3 additions & 2 deletions src/circle/HereCircleOverlayRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import type { HereActualCircle } from '../HereTypeAlias';
import { HereViewHolder } from '../HereViewHolder';
import { toGeoCoordinates } from '../GeoPoint';
import { spatialStackZIndex } from '../helpers';

export class HereCircleOverlayRenderer extends AbstractCircleOverlayRenderer<
HereViewHolder,
Expand Down Expand Up @@ -104,6 +105,6 @@ function buildCircleGeometry(state: CircleState): H.geo.Polygon | null {
}

function coerceZIndex(zIndex: number): number {
// Mirrors `(state.zIndex ?: 0).coerceIn(0, 511)` in Android.
return Math.max(0, Math.min(511, zIndex));
// Circle band, above the polygons (was `coerceIn(0, 511)`, the polygons' range).
return spatialStackZIndex('circle', zIndex);
}
27 changes: 18 additions & 9 deletions src/groundimage/HereGroundImageOverlayRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,27 @@ import type { HereActualGroundImage } from '../HereTypeAlias';
import type { HereGroundImageHandle } from './HereGroundImageHandle';
import { HereViewHolder } from '../HereViewHolder';
import { toGeoRect } from '../GeoRectBounds';

// HereMarkerRenderer.resolveDrawOrder() draws markers at
// `-lat * 1_000_000 - lng`, which ranges over roughly ±90,000,180 depending
// on position. Staying below that whole range keeps the ground image behind
// every default-positioned marker, so a translucent overlay never dims them.
const GROUND_IMAGE_Z_INDEX = -100_000_000;
import { addStackedLayer } from '../helpers';

export class HereGroundImageOverlayRenderer extends AbstractGroundImageOverlayRenderer<
HereViewHolder,
HereActualGroundImage
> {
private provider: H.map.provider.LocalObjectProvider | null = null;

/**
* Ground images live in an ObjectLayer of their own, in map.getLayers() above the rasters.
* In the map's own object layer an `H.map.Overlay` is drawn over the polygons, circles and
* polylines whatever its zIndex (it was given -100,000,000 and still covered them).
*/
private objects(): ReturnType<H.map.provider.LocalObjectProvider['getRootGroup']> {
if (this.provider == null) {
this.provider = new H.map.provider.LocalObjectProvider();
addStackedLayer(this.holder.map, new H.map.layer.ObjectLayer(this.provider), 'groundImage');
}
return this.provider.getRootGroup();
}

override async createGroundImage(
state: GroundImageState,
): Promise<HereActualGroundImage | null> {
Expand All @@ -37,10 +47,9 @@ export class HereGroundImageOverlayRenderer extends AbstractGroundImageOverlayRe
const image = await loadImage(state.imageUrl);
const overlay = new H.map.Overlay(rect, image, {
opacity: state.opacity,
zIndex: GROUND_IMAGE_Z_INDEX,
data: state.id,
});
this.holder.map.addObject(overlay);
this.objects().addObject(overlay);

const handle: HereGroundImageHandle = {
routeId: `groundimage-${state.id}`,
Expand Down Expand Up @@ -95,7 +104,7 @@ export class HereGroundImageOverlayRenderer extends AbstractGroundImageOverlayRe
entity: GroundImageEntity<HereActualGroundImage>,
): Promise<void> {
if (entity.groundImage) {
this.holder.map.removeObject((entity.groundImage as HereGroundImageHandle).overlay);
this.objects().removeObject((entity.groundImage as HereGroundImageHandle).overlay);
}
}
}
Expand Down
36 changes: 36 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { overlayStackBeforeId, overlayStackZ, type OverlayKind, type OverlayStackKey } from '@mapconductor/js-sdk-core';
/**
* Re-exports the line-model namespaces so the HERE renderer modules can import
* them from a single local path. Densify a path with
Expand All @@ -8,3 +9,38 @@ export {
WGS84Geodesic,
Planar,
} from '@mapconductor/js-sdk-core';

// ── Overlay stacking ───────────────────────────────────────────────────────
//
// Bottom to top, on every provider: raster < ground image < polygon < circle < polyline <
// marker (js-sdk-core OVERLAY_STACK_ORDER).
//
// HERE draws map objects in two places. The map's own object layer (map.addObject) is drawn
// over every layer in map.getLayers(); in it, spatial objects (polygons, circles, polylines)
// are ordered by zIndex, `H.map.Overlay` bitmaps are drawn over the spatials whatever their
// zIndex, and markers over both. Rasters are TileLayers in map.getLayers(), so already under
// the objects. Ground images therefore get an ObjectLayer of their own in map.getLayers(),
// above the rasters, and the spatials get bands of zIndex.

/** zIndex of a spatial object (polygon, circle, polyline): one band of 1024 per kind. */
export function spatialStackZIndex(kind: 'polygon' | 'circle' | 'polyline', zIndex: number | null | undefined): number {
return overlayStackZ(kind, zIndex, { bandSize: 1024 });
}

const layerStackKeys = new WeakMap<object, OverlayStackKey>();
let layerStackSequence = 0;

/**
* Adds a layer to map.getLayers() at its place in the stack: rasters (by zIndex), then the
* ground-image layer, then marker tiles. Layers that are not ours (the base layer) are left
* where they are.
*/
export function addStackedLayer(map: H.Map, layer: H.map.layer.Layer, kind: OverlayKind, zIndex: number | null | undefined = 0): void {
const key: OverlayStackKey = { kind, zIndex: zIndex ?? 0, sequence: ++layerStackSequence };
layerStackKeys.set(layer, key);
const layers: unknown[] = map.getLayers()?.asArray?.() ?? [];
const ids = layers.map((_, i) => String(i));
const beforeId = overlayStackBeforeId(ids, (id) => layerStackKeys.get(layers[Number(id)] as object) ?? null, key);
if (beforeId == null) map.addLayer(layer);
else map.addLayer(layer, Number(beforeId));
}
8 changes: 7 additions & 1 deletion src/here.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ declare global {
setBaseLayer(layer: any): void;
getLayers(): any;
setLayers(layers: any): void;
addLayer(layer: any): void;
addLayer(layer: any, opt_idx?: number): void;
removeLayer(layer: any): void;

getViewModel(): map.ViewModel;
Expand Down Expand Up @@ -304,6 +304,12 @@ declare global {
constructor(options: ImageTileProviderOptions);
}

/** Holds map objects for an `H.map.layer.ObjectLayer` of its own. */
class LocalObjectProvider extends Provider {
constructor(options?: Record<string, unknown>);
getRootGroup(): { addObject(object: any): any; removeObject(object: any): any };
}

interface ImageTileProviderOptions {
uri?: string;
min?: number;
Expand Down
5 changes: 3 additions & 2 deletions src/polygon/HerePolygonOverlayRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import type { HereActualPolygon } from '../HereTypeAlias';
import { HereViewHolder } from '../HereViewHolder';
import { toGeoCoordinates } from '../GeoPoint';
import { spatialStackZIndex } from '../helpers';

export class HerePolygonOverlayRenderer extends AbstractPolygonOverlayRenderer<
HereViewHolder,
Expand Down Expand Up @@ -107,6 +108,6 @@ function buildLineString(
}

function coerceZIndex(zIndex: number): number {
// Mirrors `state.zIndex.coerceIn(0, 511)` in Android.
return Math.max(0, Math.min(511, zIndex));
// Polygon band of the shared stack (was `coerceIn(0, 511)`, shared with circles).
return spatialStackZIndex('polygon', zIndex);
}
5 changes: 3 additions & 2 deletions src/polyline/HerePolylineOverlayRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import type { HereActualPolyline } from '../HereTypeAlias';
import { HereViewHolder } from '../HereViewHolder';
import { toGeoCoordinates } from '../GeoPoint';
import { spatialStackZIndex } from '../helpers';

export class HerePolylineOverlayRenderer extends AbstractPolylineOverlayRenderer<
HereViewHolder,
Expand Down Expand Up @@ -92,6 +93,6 @@ function createStyle(state: PolylineState): H.map.StyleOptions {
}

function coerceZIndex(zIndex: number): number {
// Mirrors `(state.zIndex + 512).coerceIn(512, 1023)` in Android.
return Math.max(512, Math.min(1023, zIndex + 512));
// Polyline band, above the circles (was `(zIndex + 512).coerceIn(512, 1023)`).
return spatialStackZIndex('polyline', zIndex);
}
15 changes: 13 additions & 2 deletions src/raster/HereRasterLayerOverlayRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from '@mapconductor/js-sdk-core';
import type { HereRasterLayerHandle } from './HereRasterLayerHandle';
import type { HereViewHolder } from '../HereViewHolder';
import { addStackedLayer } from '../helpers';

interface TileSpec {
readonly urlTemplate: string;
Expand Down Expand Up @@ -98,7 +99,7 @@ export class HereRasterLayerOverlayRenderer
const tileLayer = new H.map.layer.TileLayer(provider);
const attached = state.visible !== false;
if (attached) {
this.holder.map.addLayer(tileLayer);
this.addToMap(tileLayer, state);
}

return {
Expand All @@ -121,13 +122,23 @@ export class HereRasterLayerOverlayRenderer
const shouldAttach = state.visible !== false;
if (shouldAttach === handle.attached) return;
if (shouldAttach) {
this.holder.map.addLayer(handle.layer);
this.addToMap(handle.layer, state);
} else {
this.holder.map.removeLayer(handle.layer);
}
handle.attached = shouldAttach;
}

/**
* Under the ground-image layer (rasters are the lowest band), ordered by zIndex. Marker tiles
* go above it; they still cannot reach above the polygons, which HERE draws in its object
* layer over every layer.
*/
private addToMap(layer: H.map.layer.TileLayer, state: RasterLayerState): void {
const markerTiles = state.id === 'mc-marker-tiles';
addStackedLayer(this.holder.map, layer, markerTiles ? 'marker' : 'rasterLayer', markerTiles ? 0 : state.zIndex);
}

private async removeLayer(
entity: RasterLayerEntity<ActualRasterLayer>,
): Promise<void> {
Expand Down