Skip to content

Restore copy coordinates link #417

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Aug 8, 2024
Merged
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 src/common/isIntranet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/**
* Tests the current hostname to see if it is on the intranet.
* If the hostname test fails, then tests to see if the browser can
* make a HEAD or GET request (respectively) to an intranet test URL.
* @returns - True if the hostname is on the intranet, false otherwise.
*/
export function isIntranet(): boolean {
return (
/\.loc$/i.test(location.hostname) || /^localhost$/i.test(location.hostname)
);
}

export default isIntranet;
4 changes: 0 additions & 4 deletions src/layers/MilepostLayer/arcade/Access Control.arcade
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ var shortestDistanceSnapshotDate = null;
// Initialize a loop counter
var i = 0
for (var f in fs) {
// // Write feature to console.
// Console(f)
// Get the distance between the milepost feature
// and the current Access Control feature.
var d = Distance(f, $feature)
Expand All @@ -70,8 +68,6 @@ for (var f in fs) {
i++;
}

Console(`There were ${i} features returned.`)

if (shortestDistanceCode == null) {
return null;
} else {
Expand Down
16 changes: 16 additions & 0 deletions src/layers/MilepostLayer/arcade/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isIntranet } from "../../../common/isIntranet";
import AccessControlArcade from "./Access Control.arcade?raw";
import BingMapsArcade from "./Bing Maps.arcade?raw";
import CityArcade from "./City.arcade?raw";
Expand Down Expand Up @@ -138,4 +139,19 @@ export const expressions = expressionInfoProperties.map(
(info) => new ExpressionInfo(info) as MilepostExpressionInfo,
);

/**
* Removes the SR View URL expression.
*/
const removeSrView = () => {
const x = expressions.find((expression) => expression.name === "srViewURL");
if (x) {
expressions.splice(expressions.indexOf(x), 1);
}
};

// Remove the SR View URL expression if we are not on the intranet.
if (!isIntranet()) {
removeSrView();
}

export default expressions;
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ function webMercatorToWgs1984(geom) {
var xWebMercator = geom.x;
var yWebMercator = geom.y;

Console(xWebMercator);

// Constants
var R_MAJOR = 6378137.0;

Expand Down
172 changes: 113 additions & 59 deletions src/layers/MilepostLayer/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { objectIdFieldName } from "../../elc/types";
import arcadeExpressions from "./arcade";
import type { MilepostExpressionInfo } from "./arcade";
import type SpatialReference from "@arcgis/core/geometry/SpatialReference";
import type Field from "@arcgis/core/layers/support/Field";

Expand All @@ -10,9 +10,6 @@ export const enum fieldNames {
Srmp = "Srmp",
Back = "Back",
Direction = "Direction",
// TownshipSubdivision = "Township Subdivision",
// County = "County",
// City = "City",
}

const fields = [
Expand Down Expand Up @@ -58,6 +55,28 @@ const fields = [
},
] as FieldProperties[];

const actionButtonProperties: __esri.ActionButtonProperties[] = [
{
active: false,
title: "Copy coordinates",
visible: true,
type: "button",
icon: "copy-to-clipboard",
id: "copy",
},
];

async function createActionButtons() {
const [{ default: Collection }, { default: ActionButton }] =
await Promise.all([
import("@arcgis/core/core/Collection"),
import("@arcgis/core/support/actions/ActionButton"),
]);
return new Collection<InstanceType<typeof ActionButton>>(
actionButtonProperties.map((ap) => new ActionButton(ap)),
);
}

/**
* Creates the {@link FeatureLayer} that displays located mileposts.
* @param spatialReference - The {@link SpatialReference} of the layer.
Expand All @@ -67,39 +86,74 @@ export async function createMilepostLayer(spatialReference: SpatialReference) {
const [
{ default: FeatureLayer },
{ default: FieldInfo },
{ default: SimpleRenderer },
{ default: waExtent },
{ default: labelClass },
{ highwaySignBackgroundColor, highwaySignTextColor },
] = await Promise.all([
import("@arcgis/core/layers/FeatureLayer"),
import("@arcgis/core/popup/FieldInfo"),
import("@arcgis/core/renderers/SimpleRenderer"),
import("../../WAExtent"),
import("./labelClass"),
import("../../colors"),
]);

/**
* This is the symbol for the point on the route.
* 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.
*/
const actualMPSymbol = await import(
"@arcgis/core/symbols/SimpleMarkerSymbol"
).then(
({ default: SimpleMarkerSymbol }) =>
new SimpleMarkerSymbol({
color: highwaySignBackgroundColor,
size: 12,
style: "circle",
outline: {
width: 1,
color: highwaySignTextColor,
},
}),
);
function createAndAddFieldInfoForExpression(
milepostExpressionInfo: MilepostExpressionInfo,
) {
const fieldInfo = new FieldInfo({
fieldName: `expression/${milepostExpressionInfo.name}`,
visible: !["webMercatorToWgs1984", "milepostLabel"].includes(
milepostExpressionInfo.name,
),
});
return fieldInfo;
}

const renderer = new SimpleRenderer({
symbol: actualMPSymbol,
});
/**
* 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(),
});

createActionButtons()
.then((actions) => {
popupTemplate.actions = actions;
})
.catch((error: unknown) => {
console.error("Error adding action buttons", error);
});

// Import the Arcade expressions, add them to the popup template, and then
// add them to the popup template's fieldInfos array.
import("./arcade")
.then(({ default: arcadeExpressions }) => {
popupTemplate.expressionInfos = arcadeExpressions;

// Append expressions to the PopupTemplate's fieldInfos array.
for (const xi of arcadeExpressions) {
const fieldInfo = createAndAddFieldInfoForExpression(xi);
popupTemplate.fieldInfos.push(fieldInfo);
}
popupTemplate.title =
"{Route} ({Direction}) @ {expression/milepostLabel}";
})
.catch((error: unknown) => {
console.error("Error adding Arcade expressions", error);
});

return popupTemplate;
}
/**
* This is the symbol for the point on the route.
*/
const milepostLayer = new FeatureLayer({
labelingInfo: [labelClass],
title: "Mileposts",
Expand All @@ -109,7 +163,6 @@ export async function createMilepostLayer(spatialReference: SpatialReference) {
geometryType: "point",
objectIdField: objectIdFieldName,
fullExtent: waExtent,
renderer,
spatialReference,
// Since there are no features at the beginning,
// need to add an empty array as the source.
Expand All @@ -118,39 +171,40 @@ export async function createMilepostLayer(spatialReference: SpatialReference) {
hasM: true,
});

const popupTemplate = milepostLayer.createPopupTemplate();

// Set certain fields to be hidden in the popup.
for (const element of [
fieldNames.Route,
fieldNames.Srmp,
fieldNames.Back,
fieldNames.Direction,
]) {
const fieldInfo = popupTemplate.fieldInfos.find(
(fi) => fi.fieldName === (element as string),
);

if (fieldInfo) {
fieldInfo.visible = false;
}
}

popupTemplate.expressionInfos = arcadeExpressions;
createRenderer()
.then((renderer) => {
milepostLayer.renderer = renderer;
})
.catch((error: unknown) => {
console.error("Error creating renderer", error);
});

// Append expressions to the PopupTemplate's fieldInfos array.
for (const xi of arcadeExpressions) {
popupTemplate.fieldInfos.push(
new FieldInfo({
fieldName: `expression/${xi.name}`,
visible: !["webMercatorToWgs1984", "milepostLabel"].includes(xi.name),
}),
);
}

milepostLayer.popupTemplate = popupTemplate;

popupTemplate.title = "{Route} ({Direction}) @ {expression/milepostLabel}";
milepostLayer.popupTemplate = createPopupTemplate();

return milepostLayer;
}
async function createRenderer() {
const [
{ default: SimpleMarkerSymbol },
{ default: SimpleRenderer },
{ highwaySignBackgroundColor, highwaySignTextColor },
] = await Promise.all([
import("@arcgis/core/symbols/SimpleMarkerSymbol"),
import("@arcgis/core/renderers/SimpleRenderer"),
import("../../colors"),
]);
const actualMPSymbol = new SimpleMarkerSymbol({
color: highwaySignBackgroundColor,
size: 12,
style: "circle",
outline: {
width: 1,
color: highwaySignTextColor,
},
});

const renderer = new SimpleRenderer({
symbol: actualMPSymbol,
});
return renderer;
}
20 changes: 16 additions & 4 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,14 @@ if (!testWebGL2Support()) {
popupEnabled: false,
});

import("./setupPopupActions")
.then(({ setupPopupActions }) => {
setupPopupActions(view);
})
.catch((error: unknown) => {
console.error("Failed to setupPopupActions.", error);
});

setupSidebarCollapseButton(view);

view
Expand Down Expand Up @@ -426,10 +434,14 @@ if (!testWebGL2Support()) {
});

tempAddResults.addFeatureResults.forEach((r) => {
console.error(
"There was an error adding the temporary graphic where the user clicked.",
r.error,
);
// r.error CAN be null. Esri's type def. is wrong.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (r.error != null) {
console.error(
"There was an error adding the temporary graphic where the user clicked.",
r.error,
);
}
});

// Call findNearestRouteLocations
Expand Down
72 changes: 72 additions & 0 deletions src/setupPopupActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { on } from "@arcgis/core/core/reactiveUtils";
import { webMercatorToGeographic } from "@arcgis/core/geometry/support/webMercatorUtils";

/**
* Creates a calcite-alert element.
* @returns The created calcite-alert element.
*/
function createCalciteAlert() {
const message = "Coordinates copied to clipboard.";
const alert = document.createElement("calcite-alert");
alert.kind = "success";
alert.label = message;
alert.scale = "s";
alert.autoClose = true;
alert.autoCloseDuration = "fast";
alert.placement = "top";

const messageElement = document.createElement("p");
messageElement.append(message);
messageElement.slot = "message";
alert.append(messageElement);
return alert;
}

/**
* Sets up popup actions for the given map view.
* @param view - The map view to set up popup actions for.
*/
export function setupPopupActions(view: __esri.MapView) {
const alert = createCalciteAlert();
document.body.append(alert);
const copyPointToClipboard = (point: __esri.Point) => {
const { spatialReference } = point;
if (spatialReference.isWebMercator) {
point = webMercatorToGeographic(point) as __esri.Point;
} else if (!spatialReference.isWGS84) {
throw new Error(
`Unsupported spatial reference: ${spatialReference.wkid}`,
);
}

const { x, y } = point;

navigator.clipboard
.writeText([x, y].join(","))
.then(() => {
/* __PURE__ */ console.debug("Copied coordinates to clipboard.");
alert.open = true;
})
.catch((error: unknown) => {
console.error("Failed to copy coordinates.", error);
});
};

function isPoint(g: __esri.Geometry): g is __esri.Point {
return g.type === "point";
}

const popupTriggerActionEventHandler: __esri.PopupTriggerActionEventHandler =
(event) => {
/* __PURE__ */ console.debug("trigger-action", event);
if (event.action.id === "copy") {
const feature = view.popup.selectedFeature;
if (isPoint(feature.geometry)) {
copyPointToClipboard(feature.geometry);
}
}
};
on(() => view.popup, "trigger-action", popupTriggerActionEventHandler);
}

export default setupPopupActions;