Skip to content

Commit

Permalink
feat: Mileposts located by click now show connecting line (#488)
Browse files Browse the repository at this point in the history
* feat: Mileposts located by click now show connecting line

* refactor: Add click / SRMP line offset CIM renderer

* refactor: ♻️ move milepost-point-layer to own folder
  • Loading branch information
JeffJacobson authored Nov 25, 2024
1 parent 7b0d914 commit b07437f
Show file tree
Hide file tree
Showing 10 changed files with 248 additions and 135 deletions.
47 changes: 26 additions & 21 deletions src/addGraphicsToLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,32 @@ export async function addGraphicsToLayer(
milepostLayer: FeatureLayer,
locationGraphics: Graphic[],
) {
// Add graphics to the layer and await for the edit to complete.
const editsResult = await milepostLayer.applyEdits(
{
addFeatures: locationGraphics,
},
{},
);

// Get the added features from the edits result by querying the milepost layer
// for the features with matching object IDs.
const query = milepostLayer.createQuery();
query.objectIds = editsResult.addFeatureResults.map((r) => r.objectId);
const results = await milepostLayer.queryFeatures(query);
/* __PURE__ */ console.group(addGraphicsToLayer.name, {
milepostLayer: { ...milepostLayer },
locationGraphics: locationGraphics.map((g) => g.toJSON() as unknown),
});
try {
// Add graphics to the layer and await for the edit to complete.
const editsResult = await milepostLayer.applyEdits(
{
addFeatures: locationGraphics,
},
{},
);
/* __PURE__ */ console.debug(
"editsResult",
editsResult.addFeatureResults.map((e) => ({ ...e })),
);

return results.features;
}
// Get the added features from the edits result by querying the milepost layer
// for the features with matching object IDs.
const query = milepostLayer.createQuery();
/* __PURE__ */ console.debug("query", query.toJSON());
query.objectIds = editsResult.addFeatureResults.map((r) => r.objectId);
const results = await milepostLayer.queryFeatures(query);

if (import.meta.hot) {
import.meta.hot.accept((newModule) => {
if (newModule) {
console.log("hot module replacement", newModule);
}
});
return results.features;
} finally {
/* __PURE__ */ console.groupEnd();
}
}
140 changes: 45 additions & 95 deletions src/layers/MilepostLayer/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import Collection from "@arcgis/core/core/Collection";
import type SpatialReference from "@arcgis/core/geometry/SpatialReference";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import type FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import type Field from "@arcgis/core/layers/support/Field";
import FieldInfo from "@arcgis/core/popup/FieldInfo";
import SimpleRenderer from "@arcgis/core/renderers/SimpleRenderer";
import ActionButton from "@arcgis/core/support/actions/ActionButton";
import { SimpleMarkerSymbol } from "@arcgis/core/symbols";
import waExtent from "../../WAExtent";
import { highwaySignBackgroundColor, highwaySignTextColor } from "../../colors";
import { objectIdFieldName } from "../../elc/types";
import type { MilepostExpressionInfo } from "./arcade";
import {
expressions as arcadeExpressions,
locationLinksContent,
} from "./arcade";
import labelClass from "./labelClass";

type FieldProperties = Required<ConstructorParameters<typeof Field>>[0];

Expand All @@ -25,7 +19,7 @@ export enum fieldNames {
Direction = "Direction",
}

const fields = [
export const fields = [
{
name: objectIdFieldName,
type: "oid",
Expand Down Expand Up @@ -86,101 +80,57 @@ function createActionButtons() {
}

/**
* Creates the {@link FeatureLayer} that displays located mileposts.
* @param spatialReference - The {@link SpatialReference} of the layer.
* @returns - A {@link FeatureLayer}
* A function that creates and adds field information for an expression.
* @param milepostExpressionInfo - The expression information to create the field info for.
* @returns The created field info.
*/
export function createMilepostLayer(spatialReference: SpatialReference) {
/**
* A function that creates and adds field information for an expression.
* @param milepostExpressionInfo - The expression information to create the field info for.
* @returns The created field info.
*/
function createAndAddFieldInfoForExpression(
milepostExpressionInfo: MilepostExpressionInfo,
) {
const fieldInfo = new FieldInfo({
fieldName: `expression/${milepostExpressionInfo.name}`,
visible: !["webMercatorToWgs1984", "milepostLabel"].includes(
milepostExpressionInfo.name,
),
});
return fieldInfo;
}

/**
* Creates a popup template for the milepost layer by hiding certain fields and adding arcade expressions.
* @returns The created popup template.
*/
function createPopupTemplate() {
const popupTemplate = milepostLayer.createPopupTemplate({
// Hide all of the initial fields.
// These fields are already displayed in the popup's title.
visibleFieldNames: new Set(),
});
function createAndAddFieldInfoForExpression(
milepostExpressionInfo: MilepostExpressionInfo,
) {
const fieldInfo = new FieldInfo({
fieldName: `expression/${milepostExpressionInfo.name}`,
visible: !["webMercatorToWgs1984", "milepostLabel"].includes(
milepostExpressionInfo.name,
),
});
return fieldInfo;
}

const actions = createActionButtons();
popupTemplate.actions = actions;
/**
* Creates a popup template for the milepost layer by hiding certain fields and adding arcade expressions.
* @param milepostLayer - The milepost layer.
* @returns The created popup template.
*/
export function createPopupTemplate(milepostLayer: FeatureLayer) {
const popupTemplate = milepostLayer.createPopupTemplate({
// Hide all of the initial fields.
// These fields are already displayed in the popup's title.
visibleFieldNames: new Set(),
});

// Import the Arcade expressions, add them to the popup template, and then
// add them to the popup template's fieldInfos array.
popupTemplate.expressionInfos = arcadeExpressions;
const actions = createActionButtons();
popupTemplate.actions = actions;

// Append expressions to the PopupTemplate's fieldInfos array.
for (const xi of arcadeExpressions) {
const fieldInfo = createAndAddFieldInfoForExpression(xi);
// Hide the GeoURI and SRViewURL fields.
if (["geoURI"].includes(xi.name)) {
fieldInfo.visible = false;
}
popupTemplate.fieldInfos.push(fieldInfo);
}
popupTemplate.title = "{Route} ({Direction}) @ {expression/milepostLabel}";
// Import the Arcade expressions, add them to the popup template, and then
// add them to the popup template's fieldInfos array.
popupTemplate.expressionInfos = arcadeExpressions;

if (Array.isArray(popupTemplate.content)) {
popupTemplate.content = [locationLinksContent, ...popupTemplate.content];
// Append expressions to the PopupTemplate's fieldInfos array.
for (const xi of arcadeExpressions) {
const fieldInfo = createAndAddFieldInfoForExpression(xi);
// Hide the GeoURI and SRViewURL fields.
if (["geoURI"].includes(xi.name)) {
fieldInfo.visible = false;
}
popupTemplate.fieldInfos.push(fieldInfo);
}
popupTemplate.title = "{Route} ({Direction}) @ {expression/milepostLabel}";

return popupTemplate;
if (Array.isArray(popupTemplate.content)) {
popupTemplate.content = [locationLinksContent, ...popupTemplate.content];
}
/**
* This is the symbol for the point on the route.
*/
const milepostLayer = new FeatureLayer({
labelingInfo: [labelClass],
title: "Mileposts",
id: "mileposts",
listMode: "hide",
fields: fields,
geometryType: "point",
objectIdField: objectIdFieldName,
fullExtent: waExtent,
spatialReference,
// Since there are no features at the beginning,
// need to add an empty array as the source.
source: [],
popupEnabled: true,
hasM: true,
});

milepostLayer.renderer = createRenderer();
milepostLayer.popupTemplate = createPopupTemplate();
milepostLayer.popupTemplate = popupTemplate;

return milepostLayer;
}
function createRenderer() {
const actualMPSymbol = new SimpleMarkerSymbol({
color: highwaySignBackgroundColor,
size: 12,
style: "circle",
outline: {
width: 1,
color: highwaySignTextColor,
},
});

const renderer = new SimpleRenderer({
symbol: actualMPSymbol,
});
return renderer;
return popupTemplate;
}
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import MilepostLocationRenderer from "./Milepost Location Renderer.json";
import SimpleRenderer from "@arcgis/core/renderers/SimpleRenderer";

/**
* Simple Renderer using a CIM symbol.
*/
export default SimpleRenderer.fromJSON(MilepostLocationRenderer);
38 changes: 38 additions & 0 deletions src/layers/MilepostLayer/milepost-line-layer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import { createPopupTemplate, fields } from "..";
import waExtent from "../../../WAExtent";
import { objectIdFieldName } from "../../../elc/types";
import MilepostOffsetLineRenderer from "./MilepostOffsetLineRenderer";

/**
* Creates a new feature layer that displays mileposts as lines.
* @param spatialReference - The spatial reference of the layer.
* @returns A new feature layer that displays mileposts as lines.
*/
export function createMilepostLineLayer(
spatialReference = waExtent.spatialReference,
) {
// Make a clone of the milepost point layer, as most of the properties
// will be the same aside from the geometry type and renderer.
const lineLayerProperties: __esri.FeatureLayerProperties = {
geometryType: "polyline",
title: "Near Mileposts",
fields,
objectIdField: objectIdFieldName,
id: "nearMileposts",
listMode: "hide",
fullExtent: waExtent,
spatialReference,
// Since there are no features at the beginning,
// need to add an empty array as the source.
renderer: MilepostOffsetLineRenderer,
source: [],
popupEnabled: true,
hasM: true,
};

const lineLayer = new FeatureLayer(lineLayerProperties);
lineLayer.popupTemplate = createPopupTemplate(lineLayer);

return lineLayer;
}
60 changes: 60 additions & 0 deletions src/layers/MilepostLayer/milepost-point-layer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type SpatialReference from "@arcgis/core/geometry/SpatialReference";
import FeatureLayer from "@arcgis/core/layers/FeatureLayer";
import SimpleRenderer from "@arcgis/core/renderers/SimpleRenderer";
import { SimpleMarkerSymbol } from "@arcgis/core/symbols";
import { createPopupTemplate, fields } from "..";
import waExtent from "../../../WAExtent";
import {
highwaySignBackgroundColor,
highwaySignTextColor,
} from "../../../colors";
import { objectIdFieldName } from "../../../elc/types";
import labelClass from "../labelClass";

/**
* Creates the {@link FeatureLayer} that displays located mileposts.
* @param spatialReference - The {@link SpatialReference} of the layer.
* @returns - A {@link FeatureLayer}
*/
export function createMilepostPointLayer(spatialReference: SpatialReference) {
/**
* This is the symbol for the point on the route.
*/
const milepostLayer = new FeatureLayer({
labelingInfo: [labelClass],
title: "Mileposts",
id: "mileposts",
listMode: "hide",
fields: fields,
geometryType: "point",
objectIdField: objectIdFieldName,
fullExtent: waExtent,
spatialReference,
// Since there are no features at the beginning,
// need to add an empty array as the source.
source: [],
popupEnabled: true,
hasM: true,
});

milepostLayer.renderer = createRenderer();
createPopupTemplate(milepostLayer);

return milepostLayer;
}
function createRenderer() {
const actualMPSymbol = new SimpleMarkerSymbol({
color: highwaySignBackgroundColor,
size: 12,
style: "circle",
outline: {
width: 1,
color: highwaySignTextColor,
},
});

const renderer = new SimpleRenderer({
symbol: actualMPSymbol,
});
return renderer;
}
Loading

0 comments on commit b07437f

Please sign in to comment.