Skip to content

Commit

Permalink
Merge pull request #2187 from james-rae/jamesparty
Browse files Browse the repository at this point in the history
On Demand Identify (#2187)
  • Loading branch information
dnlnashed committed May 9, 2024
2 parents e016691 + c241f06 commit 08f89fa
Show file tree
Hide file tree
Showing 36 changed files with 614 additions and 349 deletions.
28 changes: 24 additions & 4 deletions docs/api-guides/layers.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,23 +409,43 @@ Options parameter object, with descriptions following:
- An optional integer number to buffer the geometry. Is generally only useful if the geometry is a point (i.e. where a mouse click / crosshair click occurred). The number represents pixels to buffer by (so 5 would be a 10x10 pixel square around the point at the current map scale level).
- Optional result of a local hit test (a promise resolving in an array of graphic hit results). Utilized when in hybrid identify mode; will ensure any local results are excluded from the server results, avoiding duplicates.

The result object of `runIdentify()` is on the fancy side, as there are a few levels identify acts upon. The topmost array of results has an entry for each logical layer involved, including the logical `uid`, a request timestamp(`requestTime`), a loading flag (`loaded`) and promise (`loading`), and another array of individual hits (`items`) for the logical layer. The loading properties here indicate when the items array as been populated, but be aware that individual `items` still may be downloading their own data.
The result object of `runIdentify()` is on the fancy side, as there are a few levels identify acts upon. The topmost array of results has an entry for each logical layer involved, including:

- The `uid` of the logical layer
- A request timestamp (`requestTime`)
- An array of individual hits (`items`) for the logical layer
- A loaded flag (`loaded`) to indicate if `items` has been populated.
- A promise (`loading`) that resolves when `items` has been populated.
- A flag to indicate if the identify request failed (`errored`).

The loading properties here indicate when the items array as been populated, but be aware that individual `items` may still need to download their own data.

The objects in the `items` array manage each result. To limit lots of potential network calls from issuing on large uncached results, some items will need to be explicitly loaded at an approprite time. The item exposes:

- The result `data`. Will be undefined until the item is loaded.
- The expected `format` of the data. Supported values include `esri` (standard attribute format), `text`, `image`, `html`, `xml`, `json`, `unknown`.
- A `started` flag that indicates if the data has begun (or finished) loading.
- A `loaded` flag indicating the data has loaded.
- A `loading` promise that resolves once the data is loaded.
- A `load()` method that will initiate the loading of the data. It returns the `loading` promise. There is no harm in calling load on an item that has already begun or finished loading; it will not start a duplicate request.

The `items` array contains a loading flag and promise to track the download of its data, a format specification and a property to contain data that aligns to the given format. Current format values include `esri` (standard attribute format), `text`, `image`, `html`, `xml`, `json`, `unknown`.

```js
[
{
uid,
loaded,
loading,
errored,
requestTime,
items: [
{
loaded,
loading,
started,
format,
data
data,
load()
},
]
},
Expand All @@ -441,7 +461,7 @@ await result.done;
result.forEach(r => {
r.loading.then(() => {
r.items.forEach(i => {
i.loading.then(() => processResult(i));
i.load().then(() => processResult(i.format, i.data));
});
});
});
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ramp-pcar",
"version": "4.6.0",
"version": "4.7.0-beta",
"description": "RAMP4 - The Reusable Accessible Mapping Platform, is a Javascript based web mapping platform that provides a reusable, responsive and WCAG 2.1 AA compliant common viewer for the Government of Canada. ",
"type": "module",
"module": "./dist/lib/ramp.bundle.es.js",
Expand Down
3 changes: 2 additions & 1 deletion src/api/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { useAppbarStore } from '@/fixtures/appbar/store';
import { useGridStore } from '@/fixtures/grid/store';
import { LayerState, LayerType } from '@/geo/api';
import type {
IdentifyResultFormat,
MapClick,
MapMove,
RampBasemapConfig,
Expand Down Expand Up @@ -1152,7 +1153,7 @@ export class EventAPI extends APIScope {
payload: {
data: any;
uid: string;
format: string;
format: IdentifyResultFormat;
},
open?: boolean
) => {
Expand Down
5 changes: 3 additions & 2 deletions src/api/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ export * from '@/geo/map/common-map';
export * from '@/geo/map/maptip';
export * from '@/geo/map/overview-map';
export * from '@/geo/map/ramp-map';
export * from '@/geo/layer/file-utils';
export * from '@/geo/layer/ogc-utils';
export * from '@/geo/layer/support/file-utils';
export * from '@/geo/layer/support/ogc-utils';
export * from '@/geo/layer/support/identify';
export * from '@/geo/layer/layer';
export * from '@/geo/layer/layer-instance';
export * from '@/geo/layer/common-layer';
Expand Down
166 changes: 99 additions & 67 deletions src/fixtures/details/api/details.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { AttribLayer, FixtureInstance, LayerInstance } from '@/api';
import type { Graphic, IdentifyItem, IdentifyResult } from '@/geo/api';
import {
AttribLayer,
FixtureInstance,
LayerInstance,
ReactiveIdentifyFactory
} from '@/api';
import type { IdentifyItem, IdentifyResult } from '@/api';
import type { Graphic, IdentifyResultFormat } from '@/geo/api';
import { DetailsItemInstance, useDetailsStore } from '../store';

import type {
Expand Down Expand Up @@ -49,39 +55,31 @@ export class DetailsAPI extends FixtureInstance {
}

/**
* Provided with the data for a single feature, toggles the details panel directly with the feature screen.
* Provided with the data for a single feature, shows or hides details panel.
* If panel is closed or incoming data is different than current content, panel is shown.
* If panel open and incoming data is what is currently shown, panel closes.
* The `open` parameter can override the behavior.
* featureData payload (can be empty if forcing closed)
* - uid : uid string of the layer hosting the feature
* - format : structure of the data. IdentifyResultFormat value.
* - data : source information for the feature. Analogous to the data property of an IdentifyItem
*
* @param {{data: any, uid: string, format: string}} featureData
* @param {boolean | undefined} open
* @param {{data: any, uid: string, format: IdentifyResultFormat}} featureData
* @param {boolean | undefined} open can force the panel to open (true) or close (false) regardless of current panel state
* @memberof DetailsAPI
*/
toggleFeature(
featureData: { data: any; uid: string; format: string },
featureData: { data: any; uid: string; format: IdentifyResultFormat },
open: boolean | undefined
): void {
// Close the identified layers panel.
const panel = this.$iApi.panel.get('details-panel');
if (panel.isOpen) {
this.$iApi.panel.close(panel);
}

// result: is IdentifyResult class
const props: any = {
result: {
items: [
{
data: featureData.data,
format: featureData.format,
loaded: true,
loading: Promise.resolve()
}
],
uid: featureData.uid,
loading: Promise.resolve(),
loaded: true,
requestTime: Date.now()
}
};
if (open === false) {
// close panel and run away. allows a close without providing featureData
panel.close();
this.detailsStore.currentFeatureId = undefined;
return;
}

// feature ids are composed of the layer uid and feature object id
const layer: LayerInstance | undefined = this.$iApi.geo.layer.getLayer(
Expand All @@ -93,27 +91,43 @@ export class DetailsAPI extends FixtureInstance {
? featureData.data[layer?.oidField ?? '']
: JSON.stringify(featureData.data)
}`;
this.detailsStore.currentFeatureId = featureData.data
? currFeatureId
: undefined;

if (
panel.isOpen &&
currFeatureId === this.detailsStore.currentFeatureId &&
!(open === true)
) {
// panel is open, same request was fired at it, and not a force-open. Close it.
panel.close();
this.detailsStore.currentFeatureId = undefined;
return;
}

// at this point, we are showing the payload

this.detailsStore.currentFeatureId = currFeatureId;

// Check to see if the layer has a fixture config in the store.
this._loadDetailsConfig(layer);

// toggle rules based on last opened details panel
if (open === false) {
this.$iApi.panel!.close(panel);
} else if (!panel.isOpen) {
this.detailsStore.payload = [props.result];

// open the items panel
this.$iApi.panel!.open({
id: 'details-panel',
screen: 'details-screen',
props: props
});
} else {
this.$iApi.panel!.close(panel);
const fakeResult: IdentifyResult = {
items: [
ReactiveIdentifyFactory.makeRawItem(
featureData.format,
featureData.data
)
],
uid: featureData.uid,
loading: Promise.resolve(),
loaded: true,
errored: false,
requestTime: Date.now()
};

this.detailsStore.payload = [fakeResult];

if (!panel.isOpen) {
panel.open();
}
}

Expand Down Expand Up @@ -256,14 +270,28 @@ export class DetailsAPI extends FixtureInstance {
}

/**
* Reload map elements of the hilighter.
* @param items items to reload
* @param layerUid uid of layer the items belong to
* Reload map elements of the hilighter for a set of identify items.
*
* @param {IdentifyItem | Array<IdentifyItem>} items items to reload
* @param {string} layerUid uid of layer the items belong to
*/
async reloadDetailsHilight(
items: IdentifyItem | Array<IdentifyItem>,
layerUid: string
) {
// DEV NOTE: this call is not being used anymore. But since part of public API, remains
// for respectful compatibility

// TODO this method doesn't use the lastHilight flag, so in theory if a stale
// batch of identify items is passed, they will end up drawing.
// Might be easier to depreciate this method? Breaks API but
// the method is really just a shortcut to remove + add, without the
// smarter code of those methods.
// Alternate, replace all the guts with
// await removeDetailsHilight
// await hilightDetailsItems(items, layerUid)
// but that technically changes the method behavior

// hilight all provided identify items for this layer
const hItems = items instanceof Array ? items : [items];
const hilightFix: HilightAPI = this.$iApi.fixture.get('hilight');
Expand All @@ -277,9 +305,10 @@ export class DetailsAPI extends FixtureInstance {
}

/**
* Return the graphics of the given IdentifyItems.
* @param items items to hilight
* Return the graphics of the given IdentifyItems once the items have loaded.
* @param {Array<IdentifyItem>} items identify items to hilight. Items should be of ESRI format
* @param layerUid uid of layer the items belong to
* @returns {Promise<Array<Graphic>>} resolves with array of graphics
*/
async getHilightGraphics(
items: Array<IdentifyItem>,
Expand All @@ -292,15 +321,15 @@ export class DetailsAPI extends FixtureInstance {
// get all the identified Graphics
await Promise.all(
items.map(async item => {
// ensure item finishes loading
await item.loading;

const oid = item.data[layer.oidField];
const g: Graphic = await (layer as AttribLayer).getGraphic(
oid,
{
getGeom: true,
getAttribs: true,
getStyle: true
}
);
const g = await layer.getGraphic(oid, {
getGeom: true,
getAttribs: true,
getStyle: true
});
g.id = hilightFix.constructGraphicKey(
ORIGIN_DETAILS,
layerUid,
Expand All @@ -316,23 +345,26 @@ export class DetailsAPI extends FixtureInstance {
/**
* Updates hilighted graphics when the hilight toggler is toggled.
*
* @param hilightOn Whether the toggler has been turned on/off
* @param items The items that are affected by the toggle
* @param layerUid the layer UID
* @param {boolean} hilightOn Whether the toggler has been turned on/off
* @param {IdentifyItem | Array<IdentifyItem>} items The identify items to highlight. Only required if turning on
* @param {string} layerUid the layer UID that owns the items. Only required if turning on
*/
onHilightToggle(
hilightOn: boolean,
items: IdentifyItem | Array<IdentifyItem>,
layerUid: string
items?: IdentifyItem | Array<IdentifyItem>,
layerUid?: string
) {
if (hilightOn) {
// hilight got turned on
// DEV NOTE: this call is not being used anymore. But since part of public API, remains
// for respectful compatibility

this.detailsStore.hilightToggle = hilightOn;

if (hilightOn && items && layerUid) {
// hilight got turned on, and valid params provided
this.hilightDetailsItems(items, layerUid);
this.detailsStore.hilightToggle = true;
} else {
} else if (!hilightOn) {
// hilight got turned off
this.removeDetailsHilight();
this.detailsStore.hilightToggle = false;
}
}

Expand Down
22 changes: 17 additions & 5 deletions src/fixtures/details/components/result-item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@
</template>

<script setup lang="ts">
// handles the rendering of a single result item.
// has support for the different supported formats, and applying vue templates
import { useLayerStore } from '@/stores/layer';
import { GeometryType, LayerType } from '@/geo/api';
import {
Expand All @@ -121,8 +124,8 @@ import linkifyHtml from 'linkify-html';
import ESRIDefault from '../templates/esri-default.vue';
import HTMLDefault from '../templates/html-default.vue';
import type { FieldDefinition, IdentifyItem } from '@/geo/api';
import type { LayerInstance, InstanceAPI } from '@/api';
import type { FieldDefinition } from '@/geo/api';
import type { IdentifyItem, InstanceAPI, LayerInstance } from '@/api';
import type { PropType } from 'vue';
const layerStore = useLayerStore();
Expand All @@ -137,6 +140,9 @@ const watchers = ref<Array<Function>>([]);
const detailsStore = useDetailsStore();
const { t } = useI18n();
/**
* Icon string to display for this item
*/
const icon = ref<string>('');
const zoomStatus = ref<'zooming' | 'zoomed' | 'error' | 'none'>('none');
const zoomButton = ref<HTMLElement>();
Expand Down Expand Up @@ -209,9 +215,15 @@ const itemChanged = () => {
if (props.data.loaded) {
fetchIcon();
} else {
// wait for load.
props.data.loading.then(() => {
itemChanged();
// request any details download and wait.
// innards of .load() are smart enough not to double-request.
// TODO revist when we implement pagination on the result-list.vue list mode.
// if it only renders what is on current page, then only visible items should
// hit this and make load requests. But need to ensure -- hitting everything
// will cause issue #2156
props.data.load().then(() => {
fetchIcon();
});
// TODO do we need some type of updateAlert that says the screen is now
Expand Down

0 comments on commit 08f89fa

Please sign in to comment.