Skip to content

Commit

Permalink
feat: select MVT feature on reearth/core (#527)
Browse files Browse the repository at this point in the history
* feat: select MVT feature on reearth/core

* test

* fix: use computed feature

* fix ci
  • Loading branch information
keiya01 committed Mar 9, 2023
1 parent 9c890f8 commit de605d3
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 30 deletions.
3 changes: 2 additions & 1 deletion src/core/Crust/Plugins/types.ts
@@ -1,6 +1,6 @@
import type { PropsWithChildren, RefObject } from "react";

import type { Camera, ComputedFeature, Tag } from "@reearth/core/mantle";
import type { Camera, ComputedFeature, SelectedFeatureInfo, Tag } from "@reearth/core/mantle";
import type { ComputedLayer, LayerEditEvent, LayerSelectionReason } from "@reearth/core/Map";
import type { Viewport } from "@reearth/core/Visualizer";

Expand All @@ -19,6 +19,7 @@ export type Props = PropsWithChildren<{
tags?: Tag[];
selectedLayer?: ComputedLayer;
selectedFeature?: ComputedFeature;
selectedFeatureInfo?: SelectedFeatureInfo;
layerSelectionReason?: LayerSelectionReason;
viewport?: Viewport;
alignSystem?: WidgetAlignSystem;
Expand Down
5 changes: 4 additions & 1 deletion src/core/Crust/index.tsx
@@ -1,6 +1,6 @@
import type { ReactNode, RefObject } from "react";

import type { Tag } from "@reearth/core/mantle";
import type { SelectedFeatureInfo, Tag } from "@reearth/core/mantle";

import type { ComputedFeature, ComputedLayer, Feature } from "../mantle";
import type { LayerEditEvent, LayerSelectionReason } from "../Map";
Expand Down Expand Up @@ -62,6 +62,7 @@ export type Props = {
layerId?: string;
featureId?: string;
};
selectedFeatureInfo?: SelectedFeatureInfo;
tags?: Tag[];
// widgets
widgetAlignSystem?: WidgetAlignSystemType;
Expand Down Expand Up @@ -122,6 +123,7 @@ export default function Crust({
selectedReason,
selectedComputedLayer,
selectedComputedFeature,
selectedFeatureInfo,
widgetAlignSystem,
widgetAlignSystemEditing,
widgetLayoutConstraint,
Expand Down Expand Up @@ -167,6 +169,7 @@ export default function Crust({
tags={tags}
selectedLayer={selectedComputedLayer}
selectedFeature={selectedComputedFeature}
selectedFeatureInfo={selectedFeatureInfo}
layerSelectionReason={selectedReason}
viewport={viewport}
alignSystem={widgetAlignSystem}
Expand Down
27 changes: 23 additions & 4 deletions src/core/Map/Layers/hooks.test.ts
Expand Up @@ -2,6 +2,8 @@ import { renderHook } from "@testing-library/react";
import { useRef } from "react";
import { expect, test, vi } from "vitest";

import { ComputedFeature } from "../types";

import useHooks, { type Layer, type Ref } from "./hooks";

test("hooks", () => {
Expand Down Expand Up @@ -739,12 +741,23 @@ test("select", () => {

// select
handleLayerSelect.mockClear();
result.current.ref.current?.select("x", "y", { reason: "reason" });
result.current.ref.current?.select(
"x",
"y",
{ reason: "reason" },
{ feature: { id: "abc" } as ComputedFeature },
);
rerender({ layers: initialLayers });
expect(result.current.ref.current?.selectedLayer()).toEqual({
id: "x",
});
expect(handleLayerSelect).toBeCalledWith("x", "y", expect.any(Function), { reason: "reason" });
expect(handleLayerSelect).toBeCalledWith(
"x",
"y",
expect.any(Function),
{ reason: "reason" },
{ feature: { id: "abc" } },
);
expect(handleLayerSelect).toBeCalledTimes(1);

// remove reason
Expand All @@ -754,14 +767,20 @@ test("select", () => {
expect(result.current.ref.current?.selectedLayer()).toEqual({
id: "x",
});
expect(handleLayerSelect).toBeCalledWith("x", undefined, expect.any(Function), undefined);
expect(handleLayerSelect).toBeCalledWith(
"x",
undefined,
expect.any(Function),
undefined,
undefined,
);
expect(handleLayerSelect).toBeCalledTimes(1);

// delete layers
handleLayerSelect.mockClear();
rerender({ layers: [] });
expect(result.current.ref.current?.selectedLayer()).toBeUndefined();
expect(handleLayerSelect).toBeCalledWith(undefined, undefined, undefined, undefined);
expect(handleLayerSelect).toBeCalledWith(undefined, undefined, undefined, undefined, undefined);
expect(handleLayerSelect).toBeCalledTimes(1);

// select a layer that does not exist
Expand Down
42 changes: 30 additions & 12 deletions src/core/Map/Layers/hooks.ts
Expand Up @@ -16,7 +16,7 @@ import { v4 as uuidv4 } from "uuid";
import { DATA_CACHE_KEYS } from "@reearth/core/mantle/atoms/data";
import { objectFromGetter } from "@reearth/util/object";

import { computeAtom, convertLegacyLayer } from "../../mantle";
import { computeAtom, convertLegacyLayer, SelectedFeatureInfo } from "../../mantle";
import type { Atom, ComputedLayer, Layer, NaiveLayer } from "../../mantle";
import { useGet } from "../utils";

Expand Down Expand Up @@ -61,7 +61,12 @@ export type Ref = {
findByTagLabels: (...tagLabels: string[]) => LazyLayer[];
hide: (...layers: string[]) => void;
show: (...layers: string[]) => void;
select: (layerId: string | undefined, featureId?: string, reason?: LayerSelectionReason) => void;
select: (
layerId: string | undefined,
featureId?: string,
reason?: LayerSelectionReason,
info?: SelectedFeatureInfo,
) => void;
selectedLayer: () => LazyLayer | undefined;
overriddenLayers: () => OverriddenLayer[];
};
Expand Down Expand Up @@ -109,6 +114,7 @@ export default function useHooks({
featureId: string | undefined,
layer: (() => Promise<ComputedLayer | undefined>) | undefined,
reason: LayerSelectionReason | undefined,
info: SelectedFeatureInfo | undefined,
) => void;
}) {
const layerMap = useMemo(() => new Map<string, Layer>(), []);
Expand Down Expand Up @@ -648,17 +654,22 @@ function useSelection({
featureId: string | undefined,
layer: (() => Promise<ComputedLayer | undefined>) | undefined,
reason: LayerSelectionReason | undefined,
info: SelectedFeatureInfo | undefined,
) => void;
}) {
const [[selectedLayerId, selectedReason], setSelectedLayer] = useState<
[{ layerId?: string; featureId?: string } | undefined, LayerSelectionReason | undefined]
>([initialSelectedLayerId, initialSelectedReason]);
const [[selectedLayerId, selectedReason, selectedFeatureInfo], setSelectedLayer] = useState<
[
{ layerId?: string; featureId?: string } | undefined,
LayerSelectionReason | undefined,
SelectedFeatureInfo | undefined,
]
>([initialSelectedLayerId, initialSelectedReason, undefined]);

useEffect(() => {
setSelectedLayer(s => {
return initialSelectedLayerId
? s[0]?.layerId !== initialSelectedLayerId.layerId || !isEqual(s[1], initialSelectedReason)
? [initialSelectedLayerId, initialSelectedReason]
? [initialSelectedLayerId, initialSelectedReason, undefined]
: s
: !s[0]?.layerId && !s[1]
? s
Expand All @@ -685,31 +696,38 @@ function useSelection({
})
: undefined,
selectedReason,
selectedFeatureInfo,
);
}, [onLayerSelect, selectedLayerId, selectedReason, selectedLayerForRef]);
}, [onLayerSelect, selectedLayerId, selectedReason, selectedLayerForRef, selectedFeatureInfo]);

useEffect(() => {
setSelectedLayer(s =>
s[0]?.layerId && flattenedLayers?.find(l => l.id === s[0]?.layerId)
? [{ ...s[0] }, s[1]] // Force update when flattenedLayers are updated
? [{ ...s[0] }, s[1], undefined] // Force update when flattenedLayers are updated
: !s[0]?.layerId && !s[1]
? s
: [undefined, undefined],
: [undefined, undefined, undefined],
);
}, [flattenedLayers]);

const select = useCallback(
(layerId?: unknown, featureId?: unknown, options?: LayerSelectionReason) => {
(
layerId?: unknown,
featureId?: unknown,
options?: LayerSelectionReason,
info?: SelectedFeatureInfo,
) => {
if (typeof layerId === "string")
setSelectedLayer([
{
layerId: layerId || undefined,
featureId: typeof featureId === "string" ? featureId || undefined : undefined,
},
options,
info,
]);
else if (options) setSelectedLayer(s => [s[0], options]);
else setSelectedLayer(s => (!s[0] && !s[1] ? s : [undefined, undefined]));
else if (options) setSelectedLayer(s => [s[0], options, info]);
else setSelectedLayer(s => (!s[0] && !s[1] && !s[2] ? s : [undefined, undefined, undefined]));
},
[],
);
Expand Down
3 changes: 3 additions & 0 deletions src/core/Map/Layers/index.tsx
@@ -1,5 +1,7 @@
import { forwardRef, type ForwardRefRenderFunction } from "react";

import { SelectedFeatureInfo } from "@reearth/core/mantle";

import ClusteredLayers, { type Props as ClusteredLayerProps } from "../ClusteredLayers";
import type { ComputedLayer } from "../types";

Expand Down Expand Up @@ -41,6 +43,7 @@ export type Props = Omit<ClusteredLayerProps, "atomMap" | "isHidden"> & {
featureId: string | undefined,
layer: (() => Promise<ComputedLayer | undefined>) | undefined,
reason: LayerSelectionReason | undefined,
info: SelectedFeatureInfo | undefined,
) => void;
};

Expand Down
17 changes: 14 additions & 3 deletions src/core/Map/hooks.ts
@@ -1,5 +1,7 @@
import { useImperativeHandle, useRef, type Ref, useState, useCallback, useEffect } from "react";

import { SelectedFeatureInfo } from "../mantle";

import { type MapRef, mapRef } from "./ref";
import type { EngineRef, LayersRef, LayerSelectionReason, ComputedLayer } from "./types";

Expand All @@ -19,6 +21,7 @@ export default function ({
featureId: string | undefined,
layer: (() => Promise<ComputedLayer | undefined>) | undefined,
options?: LayerSelectionReason,
info?: SelectedFeatureInfo,
) => void;
}) {
const engineRef = useRef<EngineRef>(null);
Expand Down Expand Up @@ -48,6 +51,7 @@ export default function ({
featureId?: string;
layer?: ComputedLayer;
reason?: LayerSelectionReason;
info?: SelectedFeatureInfo;
}>({});

const handleLayerSelect = useCallback(
Expand All @@ -56,15 +60,21 @@ export default function ({
featureId: string | undefined,
layer: (() => Promise<ComputedLayer | undefined>) | undefined,
reason?: LayerSelectionReason,
info?: SelectedFeatureInfo,
) => {
selectLayer({ layerId, featureId, layer: await layer?.(), reason });
selectLayer({ layerId, featureId, layer: await layer?.(), reason, info });
},
[],
);

const handleEngineLayerSelect = useCallback(
(layerId: string | undefined, featureId?: string, reason?: LayerSelectionReason) => {
layersRef.current?.select(layerId, featureId, reason);
(
layerId: string | undefined,
featureId?: string,
reason?: LayerSelectionReason,
info?: SelectedFeatureInfo,
) => {
layersRef.current?.select(layerId, featureId, reason, info);
},
[],
);
Expand All @@ -75,6 +85,7 @@ export default function ({
selectedLayer.featureId,
async () => selectedLayer.layer,
selectedLayer.reason,
selectedLayer.info,
);
}, [onLayerSelect, selectedLayer]);

Expand Down
10 changes: 9 additions & 1 deletion src/core/Map/types/index.ts
Expand Up @@ -7,7 +7,14 @@ import type {
RefObject,
} from "react";

import type { LatLngHeight, Camera, Rect, LatLng, DataType } from "../../mantle";
import type {
LatLngHeight,
Camera,
Rect,
LatLng,
DataType,
SelectedFeatureInfo,
} from "../../mantle";
import type {
FeatureComponentType,
ClusterComponentType,
Expand Down Expand Up @@ -113,6 +120,7 @@ export type EngineProps = {
layerId: string | undefined,
featureId?: string,
options?: LayerSelectionReason,
info?: SelectedFeatureInfo,
) => void;
onCameraChange?: (camera: Camera) => void;
onLayerDrag?: (layerId: string, featureId: string | undefined, position: LatLng) => void;
Expand Down
5 changes: 3 additions & 2 deletions src/core/Visualizer/hooks.ts
Expand Up @@ -5,7 +5,7 @@ import { type DropOptions, useDrop } from "@reearth/util/use-dnd";

import type { Block, BuiltinWidgets } from "../Crust";
import { getBuiltinWidgetOptions } from "../Crust/Widgets/Widget";
import type { ComputedFeature, Feature, LatLng } from "../mantle";
import type { ComputedFeature, Feature, LatLng, SelectedFeatureInfo } from "../mantle";
import type {
Ref as MapRef,
LayerSelectionReason,
Expand Down Expand Up @@ -102,11 +102,12 @@ export default function useHooks({
featureId: string | undefined,
layer: (() => Promise<ComputedLayer | undefined>) | undefined,
reason: LayerSelectionReason | undefined,
info: SelectedFeatureInfo | undefined,
) => {
const computedLayer = await layer?.();

selectFeature(computedLayer?.originalFeatures.find(f => f.id === featureId));
selectComputedFeature(computedLayer?.features.find(f => f.id === featureId));
selectComputedFeature(computedLayer?.features.find(f => f.id === featureId) ?? info?.feature);

selectLayer({ layerId, featureId, layer: computedLayer, reason });
},
Expand Down
18 changes: 15 additions & 3 deletions src/core/engines/Cesium/Feature/Raster/hooks.ts
@@ -1,5 +1,10 @@
import { VectorTileFeature } from "@mapbox/vector-tile";
import { ImageryLayerCollection, ImageryProvider, WebMapServiceImageryProvider } from "cesium";
import {
ImageryLayerCollection,
ImageryLayerFeatureInfo,
ImageryProvider,
WebMapServiceImageryProvider,
} from "cesium";
import { MVTImageryProvider } from "cesium-mvt-imagery-provider";
import md5 from "js-md5";
import { isEqual, pick } from "lodash-es";
Expand Down Expand Up @@ -165,10 +170,17 @@ export const useMVT = ({
};
},
onSelectFeature: (mvtFeature, tile) => {
// TODO: Implement select feature
const _id = mvtFeature.id
const layer = extractSimpleLayer(cachedCalculatedLayerRef.current?.layer);
if (!layer) {
return;
}
const id = mvtFeature.id
? String(mvtFeature.id)
: idFromGeometry(mvtFeature.loadGeometry(), tile);
const feature = evalFeature(layer, makeFeatureFromPolygon(id, mvtFeature, tile));
const info = new ImageryLayerFeatureInfo();
info.data = { layerId: layer?.id, featureId: id, feature };
return info;
},
});
}, [
Expand Down
13 changes: 10 additions & 3 deletions src/core/engines/Cesium/hooks.ts
Expand Up @@ -15,7 +15,7 @@ import type { CesiumComponentRef, CesiumMovementEvent, RootEventTarget } from "r
import { useCustomCompareCallback } from "use-custom-compare";

import { e2eAccessToken, setE2ECesiumViewer } from "@reearth/config";
import { ComputedFeature, DataType } from "@reearth/core/mantle";
import { ComputedFeature, DataType, SelectedFeatureInfo } from "@reearth/core/mantle";
import { LayersRef } from "@reearth/core/Map";

import type {
Expand Down Expand Up @@ -61,7 +61,12 @@ export default ({
selectionReason?: LayerSelectionReason;
isLayerDraggable?: boolean;
meta?: Record<string, unknown>;
onLayerSelect?: (layerId?: string, featureId?: string, options?: LayerSelectionReason) => void;
onLayerSelect?: (
layerId?: string,
featureId?: string,
options?: LayerSelectionReason,
info?: SelectedFeatureInfo,
) => void;
onCameraChange?: (camera: Camera) => void;
onLayerDrag?: (layerId: string, featureId: string | undefined, position: LatLng) => void;
onLayerDrop?: (
Expand Down Expand Up @@ -379,7 +384,9 @@ export default ({
if (!pickRay) return;
scene.imageryLayers.pickImageryLayerFeatures(pickRay, scene)?.then(l => {
l.map(f => {
onLayerSelect?.(f.data.layerId, f.data.featureId);
onLayerSelect?.(f.data.layerId, f.data.featureId, undefined, {
feature: f.data.feature,
});
});
});
}
Expand Down

0 comments on commit de605d3

Please sign in to comment.