Skip to content

Commit

Permalink
Merge pull request #6945 from TerriaJS/geojson-timeseries
Browse files Browse the repository at this point in the history
Geojson/protomaps time-series support + fix mismatched IDs
  • Loading branch information
nf-s authored Oct 25, 2023
2 parents 045e7b4 + dc01dff commit 59e658a
Show file tree
Hide file tree
Showing 9 changed files with 542 additions and 91 deletions.
6 changes: 6 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
- Fix `WebMapServiceCatalogItem` `allowFeaturePicking`
- Allow translation of TableStylingWorkflow.
- Fix "Remove all" not removing selected/picked features
- Fix crash on empty GeoJSON features
- Add `tableFeatureInfoContext` support to `GeoJsonMixin.createProtomapsImageryProvider`
- Fix `GeoJsonMixin` timeline animation for lines/polygons
- Fix bug in mismatched GeoJSON Feature `_id_` and TableMixin `rowId` - this was causing incorrect styling when using `filterByProperties` or features had `null` geometry
- Fix splitter for `GeoJsonMixin` (lines and polygon features only)
- Fix share links with picked features from `ProtomapsImageryProvider`
- [The next improvement]

#### 8.3.6 - 2023-10-03
Expand Down
119 changes: 72 additions & 47 deletions lib/Map/ImageryProvider/ProtomapsImageryProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,35 @@ import circle from "@turf/circle";
import { Feature } from "@turf/helpers";
import i18next from "i18next";
import { cloneDeep, isEmpty } from "lodash-es";
import { action, observable, runInAction, makeObservable } from "mobx";
import { action, makeObservable, observable, runInAction } from "mobx";
import {
Bbox,
Feature as ProtomapsFeature,
GeomType,
Labelers,
LabelRule,
Labelers,
LineSymbolizer,
painter,
Rule as PaintRule,
PmtilesSource,
PreparedTile,
Rule as PaintRule,
Feature as ProtomapsFeature,
TileCache,
TileSource,
View,
Zxy,
ZxySource
ZxySource,
painter
} from "protomaps";
import Cartographic from "terriajs-cesium/Source/Core/Cartographic";
import Credit from "terriajs-cesium/Source/Core/Credit";
import defaultValue from "terriajs-cesium/Source/Core/defaultValue";
import DeveloperError from "terriajs-cesium/Source/Core/DeveloperError";
import CesiumEvent from "terriajs-cesium/Source/Core/Event";
import CesiumMath from "terriajs-cesium/Source/Core/Math";
import Rectangle from "terriajs-cesium/Source/Core/Rectangle";
import WebMercatorTilingScheme from "terriajs-cesium/Source/Core/WebMercatorTilingScheme";
import defaultValue from "terriajs-cesium/Source/Core/defaultValue";
import ImageryLayerFeatureInfo from "terriajs-cesium/Source/Scene/ImageryLayerFeatureInfo";
import filterOutUndefined from "../../Core/filterOutUndefined";
import isDefined from "../../Core/isDefined";
import TerriaError from "../../Core/TerriaError";
import isDefined from "../../Core/isDefined";
import {
FeatureCollectionWithCrs,
FEATURE_ID_PROP as GEOJSON_FEATURE_ID_PROP,
Expand Down Expand Up @@ -85,6 +84,8 @@ export type ProtomapsData = string | FeatureCollectionWithCrs | Source;
interface Options {
terria: Terria;

/** This must be defined to support pickedFeatures in share links */
id?: string;
data: ProtomapsData;
minimumZoom?: number;
maximumZoom?: number;
Expand All @@ -96,6 +97,10 @@ interface Options {

/** The name of the property that is a unique ID for features */
idProperty?: string;

processPickedFeatures?: (
features: ImageryLayerFeatureInfo[]
) => Promise<ImageryLayerFeatureInfo[]>;
}

/** Buffer (in pixels) used when rendering (and generating - through geojson-vt) vector tiles */
Expand Down Expand Up @@ -268,6 +273,10 @@ export default class ProtomapsImageryProvider
readonly errorEvent = new CesiumEvent();
readonly ready = true;
readonly credit: Credit;
/** This is only used for Terria feature picking - as we track ImageryProvider feature picking by url (See PickedFeatures/Cesium._attachProviderCoordHooks). This URL is never called.
* This is set using the `id` property in the constructor options
*/
readonly url?: string;

// Set values to please poor cesium types
readonly defaultNightAlpha = undefined;
Expand All @@ -288,11 +297,14 @@ export default class ProtomapsImageryProvider
// Protomaps properties
/** Data object from constructor options (this is transformed into `source`) */
private readonly data: ProtomapsData;
readonly maximumNativeZoom: number;
private readonly labelers: Labelers;
private readonly view: View | undefined;
readonly idProperty: string;
private readonly processPickedFeatures?: (
features: ImageryLayerFeatureInfo[]
) => Promise<ImageryLayerFeatureInfo[]>;

readonly maximumNativeZoom: number;
readonly idProperty: string;
readonly source: Source;
readonly paintRules: PaintRule[];
readonly labelRules: LabelRule[];
Expand Down Expand Up @@ -342,6 +354,7 @@ export default class ProtomapsImageryProvider
}

this.errorEvent = new CesiumEvent();
this.url = options.id;

this.ready = true;

Expand Down Expand Up @@ -399,6 +412,8 @@ export default class ProtomapsImageryProvider
16,
() => undefined
);

this.processPickedFeatures = options.processPickedFeatures;
}

getTileCredits(x: number, y: number, level: number): Credit[] {
Expand Down Expand Up @@ -490,6 +505,7 @@ export default class ProtomapsImageryProvider
longitude: number,
latitude: number
): Promise<ImageryLayerFeatureInfo[]> {
const featureInfos: ImageryLayerFeatureInfo[] = [];
// If view is set - this means we are using actual vector tiles (that is not GeoJson object)
// So we use this.view.queryFeatures
if (this.view) {
Expand All @@ -498,47 +514,48 @@ export default class ProtomapsImageryProvider
(r) => r.dataLayer
);

return filterOutUndefined(
this.view
.queryFeatures(
CesiumMath.toDegrees(longitude),
CesiumMath.toDegrees(latitude),
level
this.view
.queryFeatures(
CesiumMath.toDegrees(longitude),
CesiumMath.toDegrees(latitude),
level
)
.forEach((f) => {
// Only create FeatureInfo for visible features with properties
if (
!f.feature.props ||
isEmpty(f.feature.props) ||
!renderedLayers.includes(f.layerName)
)
.map((f) => {
// Only create FeatureInfo for visible features with properties
if (
!f.feature.props ||
isEmpty(f.feature.props) ||
!renderedLayers.includes(f.layerName)
)
return;

const featureInfo = new ImageryLayerFeatureInfo();

// Add Layer name property
featureInfo.properties = Object.assign(
{ [LAYER_NAME_PROP]: f.layerName },
f.feature.props ?? {}
);
featureInfo.position = new Cartographic(longitude, latitude);
return;

featureInfo.configureDescriptionFromProperties(f.feature.props);
featureInfo.configureNameFromProperties(f.feature.props);
const featureInfo = new ImageryLayerFeatureInfo();

// Add Layer name property
featureInfo.properties = Object.assign(
{ [LAYER_NAME_PROP]: f.layerName },
f.feature.props ?? {}
);
featureInfo.position = new Cartographic(longitude, latitude);

featureInfo.configureDescriptionFromProperties(f.feature.props);
featureInfo.configureNameFromProperties(f.feature.props);

featureInfos.push(featureInfo);
});

return featureInfo;
})
);
// No view is set and we have geoJSON object
// So we pick features manually
} else if (
this.source instanceof GeojsonSource &&
this.source.geojsonObject
) {
// Get rough meters per pixel (at equator) for given zoom level
const zoomMeters = 156543 / Math.pow(2, level);
// Create circle with 10 pixel radius to pick features
const buffer = circle(
[CesiumMath.toDegrees(longitude), CesiumMath.toDegrees(latitude)],
10 * this.terria.mainViewer.scale,
10 * zoomMeters,
{
steps: 10,
units: "meters"
Expand All @@ -556,12 +573,12 @@ export default class ProtomapsImageryProvider
const bufferBbox = bbox(buffer);

// Get array of all features
let features: Feature[] = this.source.geojsonObject.features;
let geojsonFeatures: Feature[] = this.source.geojsonObject.features;

const pickedFeatures: Feature[] = [];

for (let index = 0; index < features.length; index++) {
const feature = features[index];
for (let index = 0; index < geojsonFeatures.length; index++) {
const feature = geojsonFeatures[index];
if (!feature.bbox) {
feature.bbox = bbox(feature);
}
Expand Down Expand Up @@ -591,7 +608,7 @@ export default class ProtomapsImageryProvider
}

// Convert pickedFeatures to ImageryLayerFeatureInfos
return pickedFeatures.map((f) => {
pickedFeatures.forEach((f) => {
const featureInfo = new ImageryLayerFeatureInfo();

featureInfo.data = f;
Expand All @@ -611,10 +628,15 @@ export default class ProtomapsImageryProvider
featureInfo.configureDescriptionFromProperties(f.properties);
featureInfo.configureNameFromProperties(f.properties);

return featureInfo;
featureInfos.push(featureInfo);
});
}
return [];

if (this.processPickedFeatures) {
return await this.processPickedFeatures(featureInfos);
}

return featureInfos;
}

private clone(options?: Partial<Options>) {
Expand Down Expand Up @@ -648,14 +670,17 @@ export default class ProtomapsImageryProvider

return new ProtomapsImageryProvider({
terria: options?.terria ?? this.terria,
id: options?.id ?? this.url,
data,
minimumZoom: options?.minimumZoom ?? this.minimumLevel,
maximumZoom: options?.maximumZoom ?? this.maximumLevel,
maximumNativeZoom: options?.maximumNativeZoom ?? this.maximumNativeZoom,
rectangle: options?.rectangle ?? this.rectangle,
credit: options?.credit ?? this.credit,
paintRules: options?.paintRules ?? this.paintRules,
labelRules: options?.labelRules ?? this.labelRules
labelRules: options?.labelRules ?? this.labelRules,
processPickedFeatures:
options?.processPickedFeatures ?? this.processPickedFeatures
});
}

Expand Down
Loading

0 comments on commit 59e658a

Please sign in to comment.