let view, facilityLocLyr, selectLyr, highlightLyr, favoritesLyr, favorites, placeholder, listSubmit, addToListAction, facilityZoomTo, filterFacilities, resetButton, selectModal, selectConfirm, selectCancel, updateRequests, sketch; let showActionLabels = false; const suggestions = new Array(); let starSolid = ""; let starOutline = ""; let pegman = ""; const facilityPath = "M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"; const starPath = "M316.9 18C311.6 7 300.4 0 288.1 0s-23.4 7-28.8 18L195 150.3 51.4 171.5c-12 1.8-22 10.2-25.7 21.7s-.7 24.2 7.9 32.7L137.8 329 113.2 474.7c-2 12 3 24.2 12.9 31.3s23 8 33.8 2.3l128.3-68.5 128.3 68.5c10.8 5.7 23.9 4.9 33.8-2.3s14.9-19.3 12.9-31.3L438.5 329 542.7 225.9c8.6-8.5 11.7-21.2 7.9-32.7s-13.7-19.9-25.7-21.7L381.2 150.3 316.9 18z"; const favoriteSymbol = { type: "simple-marker", path: starPath, size: 24, color: [219, 150, 22], outline: { width: 1, color: [245, 223, 59], }, }; const miscSymbol = { type: "simple-marker", style: "circle", size: 20, color: [0, 100, 100], outline: { width: 0 }, }; const parcelHistoryURL = "https://services.arcgis.com/RmCCgQtiZLDCtblq/arcgis/rest/services/Parcel_Change_File/FeatureServer/0/query"; const hideFrames = () => { document.getElementById("iframe-container").hidden = true; document.querySelectorAll(".content > iframe").forEach((frame) => { frame.src = ""; frame.hidden = true; }); }; const showFrame = (id, url) => { hideFrames(); const frame = document.getElementById(id); frame.src = url; frame.hidden = false; document.getElementById("iframe-container").hidden = false; }; require([ "esri/config", "esri/Graphic", "esri/rest/query", "esri/geometry/Point", "esri/layers/FeatureLayer", "esri/layers/GraphicsLayer", "esri/layers/GroupLayer", "esri/layers/TileLayer", "esri/Map", "esri/views/MapView", "esri/widgets/BasemapToggle", "esri/widgets/Expand", "esri/widgets/Home", "esri/widgets/Legend", "esri/widgets/Search", "esri/widgets/Sketch", ], ( esriConfig, Graphic, query, Point, FeatureLayer, GraphicsLayer, GroupLayer, TileLayer, Map, MapView, BasemapToggle, Expand, Home, Legend, Search, Sketch ) => { // Set image URLs when page loaded starSolid = document.getElementById("star-outline-url").href; starOutline = document.getElementById("star-solid-url").href; pegman = document.getElementById("pegman-url").href; const formatParcel = (parcel) => { const re = /[^0-9]/g; ain = parcel.replace(re, ""); if (ain.length != 10) { return parcel; } const apn = [ain.substr(0, 4)]; apn.push(ain.substr(4, 3)); apn.push(ain.substr(7, 3)); return apn.join("-"); }; const selectParcels = async (featureArr) => { // featureDict (Array): { // "feature": feature, // "oldAIN" (optional): search term (str) // } const features = new Array(); for (const item of featureArr) { const { feature } = item; let content = ""; const ain = feature.attributes.AIN; const oldAin = item.oldAIN ? formatParcel(item.oldAIN) : undefined; if (oldAin && oldAin != ain) { content = `
Parcel ${oldAin} no longer exists. It has been split, joined, or renamed, and now belongs to parcel ${ain}
`; } feature.popupTemplate = parcelPopup(feature, (content = content)); features.push(feature); } await view.goTo(features); view.popup.open({ features: features, location: findLabelPt(features[0].geometry), featureMenuOpen: true, }); }; const selectFacilities = async (features) => { for (const feature of features) { feature.popupTemplate = facilityPopup(feature); } let target; if (features.length > 1) { target = features; } else { target = { target: features[0], scale: 1_200, }; } await view.goTo(target); view.popup.open({ features: features, location: view.center, featureMenuOpen: true, }); }; const selectMisc = async (feature, title) => { feature.popupTemplate = miscPopup(title); feature.symbol = miscSymbol; const target = { target: feature, scale: 1_200, }; await view.goTo(target); view.popup.open({ features: [feature], location: findLabelPt(feature.geometry), featureMenuOpen: true, }); }; const getParcelFacilities = async (ain) => { ain = ain.replace(/[^0-9]/g, ""); let parcelFacilities = new Array(); // Get parcel(s) where AIN = ain const facParcelsQuery = { where: `AIN = '${ain}'`, outFields: "*", }; const facilityParcels = await query.executeQueryJSON( facilitiesParcelsURL, facParcelsQuery ); let addressFacilities = { features: new Array() }; if (facilityParcels.features.length) { // Get facilities related to parcels const parcelFacilities = facilityParcels.features.map( (feature) => `'${feature.attributes.FACILITY_ID}'` ); const addressQuery = { where: `FACILITY_ID IN (${parcelFacilities.join(", ")})`, outFields: "*", }; addressFacilities = await query.executeQueryJSON( facilitiesURL, addressQuery ); } let addresses = new Array(); if (addressFacilities.features.length) { // Get addresses related to facilities addresses = addressFacilities.features.map( (feature) => `'${feature.attributes.Address}'` ); const locationQuery = { where: [ facilityLocLyr.definitionExpression, `Address IN (${addresses.join(", ")})`, ].join(" AND "), outFields: "*", returnGeometry: true, }; const addressLocations = await facilityLocLyr.queryFeatures( locationQuery ); if (addressLocations.features.length) { parcelFacilities = addressLocations.features; } } // Add any facilities contained within parcel boundary, // even if not related to parcel const parcelQuery = { where: `AIN = '${ain}'`, outFields: "*", returnGeometry: true, }; const parcelFeatures = await parcelsLyr.queryFeatures(parcelQuery); if (parcelFeatures.features.length) { const parcelGeom = parcelFeatures.features[0].geometry; const overlappingWhere = [facilityLocLyr.definitionExpression]; if (addresses.length) { overlappingWhere.push(`Address NOT IN (${addresses.join(", ")})`); } const overlappingQuery = { where: overlappingWhere.join(" AND "), outFields: "*", returnGeometry: true, spatialRelationship: "intersects", geometry: parcelGeom, }; const overlapping = await facilityLocLyr.queryFeatures(overlappingQuery); for (const overlap of overlapping.features) { parcelFacilities.push(overlap); } } return parcelFacilities; }; const getActiveParcel = async (ain) => { ain = ain.replace(/[^0-9]/g, ""); const changeIDQuery = { where: `AIN = '${ain}'`, outFields: ["ParcelChangeID"], f: "json", }; const changeIDResults = await query.executeQueryJSON( parcelHistoryURL, changeIDQuery ); const changeIDs = changeIDResults.features.map( (feature) => feature.attributes.ParcelChangeID ); if (changeIDs.length) { const parcelQuery = { where: `ParcelChangeID IN (${changeIDs.join( ", " )}) AND currentParcelStatus = 'Active'`, outFields: ["AIN"], }; const parcelResults = await query.executeQueryJSON( parcelHistoryURL, parcelQuery ); if (parcelResults.features.length) { return parcelResults.features[0].attributes.AIN; } } return undefined; }; placeholder = document.getElementById("favorites-placeholder"); const setFavorite = (imgPath) => { const selected = view.popup.selectedFeature; if (!selected || !selected.layer == facilityLocLyr) { return; } const actions = selected.popupTemplate.actions; let fave; for (const action of actions) { if (action.id == addToListAction.id) { fave = action; break; } } if (!fave) { return; } fave.image = imgPath; const contentStar = document.querySelector(".popup-content-star"); if (contentStar) { contentStar.src = imgPath; } const favoriteWord = document.querySelector(".popup-content-favorite-word"); if (favoriteWord) { if (imgPath == starSolid) { favoriteWord.textContent = "unfavorite"; } else { favoriteWord.textContent = "favorite"; } } }; const favoriteOn = () => { setFavorite(starSolid); }; const favoriteOff = () => { setFavorite(starOutline); }; const updateFavoriteCount = () => { const favoriteCount = document.getElementById("favorites-count"); favoriteCount.textContent = document.querySelectorAll( "#favorites-list calcite-list-item" ).length; }; const removeFavorite = (oid) => { const listSize = favorites.querySelectorAll("calcite-list-item").length; // If only item in list, replace list with placeholder and disable clear/submit buttons if (listSize == 1) { favorites.hidden = true; placeholder.hidden = false; resetButton.disabled = true; listSubmit.disabled = true; facilityZoomTo.disabled = true; } // Remove list item const listItem = document.getElementById(`oid-${oid}`); const addr = listItem.label; listItem.remove(); // Remove OID and address from local storage favorites let favoriteOIDs = JSON.parse(Storage.getItem("favorite-oids", "[]")); let favoriteAddresses = JSON.parse( Storage.getItem("favorite-addresses", "[]") ); favoriteOIDs = favoriteOIDs.filter((objectid) => objectid != oid); favoriteAddresses = favoriteAddresses.filter((address) => address != addr); Storage.setItem("favorite-oids", JSON.stringify(favoriteOIDs)); Storage.setItem("favorite-addresses", JSON.stringify(favoriteAddresses)); // Change "favorite" symbol in popup if necessary if (view.popup.visible) { const selected = view.popup.selectedFeature; if (selected && [facilityLocLyr, favoritesLyr].includes(selected.layer)) { const popupOID = selected.attributes.ObjectId; if (popupOID == oid) { favoriteOff(); } } } // Remove map graphic favoritesLyr.graphics = favoritesLyr.graphics.filter( (graphic) => graphic.attributes.ObjectId != oid ); // Update facilities layer to avoid duplicate symbology on new favorite filterFacilities(); }; const addFavorite = (feature) => { const oid = feature.attributes.ObjectId; const address = feature.attributes.Address; // Add OID and address to favorites list in local storage const favoriteOIDs = JSON.parse(Storage.getItem("favorite-oids", "[]")); const favoriteAddresses = JSON.parse( Storage.getItem("favorite-addresses", "[]") ); favoriteOIDs.push(oid); favoriteAddresses.push(address); Storage.setItem("favorite-oids", JSON.stringify(favoriteOIDs)); Storage.setItem("favorite-addresses", JSON.stringify(favoriteAddresses)); // Do not add if already in list for (const favorite of favorites.querySelectorAll("calcite-list-item")) { if (favorite.id == `oid-${oid}`) { return; } } // Show list and enable submit and clear list buttons favorites.hidden = false; placeholder.hidden = true; resetButton.disabled = false; listSubmit.disabled = false; facilityZoomTo.disabled = false; // Add favorite marker to feature const graphic = new Graphic(); graphic.geometry = feature.geometry; graphic.attributes = feature.attributes; graphic.symbol = favoriteSymbol; favoritesLyr.graphics.add(graphic); // Create list item const listItem = document.createElement("calcite-list-item"); listItem.id = `oid-${oid}`; listItem.setAttribute("label", address); listItem.setAttribute("value", oid); const zoomAction = document.createElement("calcite-action"); zoomAction.setAttribute("slot", "actions-start"); zoomAction.setAttribute("icon", "magnifying-glass-plus"); zoomAction.id = `${oid}-zoom`; const zoomActionTooltip = document.createElement("calcite-tooltip"); zoomActionTooltip.setAttribute("reference-element", zoomAction.id); zoomActionTooltip.innerHTML = "Zoom to"; zoomAction.appendChild(zoomActionTooltip); listItem.appendChild(zoomAction); const trashAction = document.createElement("calcite-action"); trashAction.setAttribute("slot", "actions-end"); trashAction.setAttribute("icon", "trash"); trashAction.id = `${oid}-trash`; const trashActionTooltip = document.createElement("calcite-tooltip"); trashActionTooltip.setAttribute("reference-element", trashAction.id); trashActionTooltip.innerHTML = "Remove from list"; trashAction.appendChild(trashActionTooltip); listItem.appendChild(trashAction); favorites.appendChild(listItem); // Add select event to list item listItem.addEventListener("calciteListItemSelect", () => { selectFacilities([graphic]); }); // Add click events to list item actions zoomAction.addEventListener("click", () => { view.goTo({ target: favoritesLyr.graphics.filter( (graphic) => graphic.attributes.ObjectId == oid ), zoom: view.zoom + 1, }); }); trashAction.addEventListener("click", () => { removeFavorite(oid); // Log favorite removal logAction({ action: "favorite removed", location: trashAction.parentElement.label, }); }); // Update facilities layer to avoid duplicate symbology on new favorite filterFacilities(); }; (async () => { await customElements.whenDefined("calcite-modal"); selectModal = document.getElementById("confirm-select-modal"); await selectModal.componentOnReady(); selectModal.addEventListener("calciteModalBeforeClose", () => { highlightLyr.graphics = []; }); })(); (async () => { await customElements.whenDefined("calcite-button"); selectConfirm = document.getElementById("confirm-select-yes"); selectCancel = document.getElementById("confirm-select-no"); listSubmit = document.getElementById("list-submit"); await selectConfirm.componentOnReady(); await selectCancel.componentOnReady(); await listSubmit.componentOnReady(); selectCancel.addEventListener("click", () => { selectModal.open = false; }); selectConfirm.addEventListener("click", () => { // Add selected features to favorites list for (const graphic of highlightLyr.graphics) { addFavorite(graphic); } // Close modal selectModal.open = false; // Open favorites list searches.expanded = true; // Log favorites added from selection const logFeatures = highlightLyr.graphics.map( (graphic) => graphic.attributes.Address ); const logData = { action: "favorite(s) from map selection", n_favorites: highlightLyr.graphics.length, features: logFeatures.join("; "), }; logAction(logData); }); listSubmit.addEventListener("click", () => { updateRequests(favoritesLyr.graphics).then(() => { const { href } = document.getElementById("records-request-url"); showFrame("records-request-frame", href); // Log favorites list records request form open const logData = { action: "Favorites list records request", n_favorites: document .getElementById("favorites-list") .querySelectorAll("calcite-list-item").length, }; logAction(logData); }); }); })(); (async () => { await customElements.whenDefined("calcite-action"); resetButton = document.getElementById("clear-favorites"); await resetButton.componentOnReady(); await customElements.whenDefined("calcite-list"); favorites = document.getElementById("favorites-list"); await favorites.componentOnReady(); resetButton.addEventListener("click", () => { // Remove each item from list for (const listItem of favorites.querySelectorAll("calcite-list-item")) { removeFavorite(listItem.id.split("-")[1]); } // Log favorites list reset logAction({ action: "favorites cleared", }); }); facilityZoomTo = document.getElementById("zoom-to-favorites"); await facilityZoomTo.componentOnReady(); facilityZoomTo.addEventListener("click", () => { view.goTo(favoritesLyr.graphics); }); const infoBtn = document.getElementById("info-button"); await infoBtn.componentOnReady(); infoBtn.addEventListener("click", () => { document.getElementById("info-modal").open = true; }); })(); // ---------------------------------------- Local Storage --------------------------------------- // Remove any previous favorited facilities Storage.setItem("favorite-oids", "[]"); Storage.setItem("favorite-addresses", "[]"); Storage.setItem("request-addresses", "{}"); let storeExtent = Storage.getItem("extent", false); let storeLegendExpanded = Storage.getItem("legendExpanded", true); let storeBasemap = Storage.getItem("basemap", "streets-navigation-vector"); let storeShowStore = Storage.getItem("showStore", null); let storeShouldStore = Storage.getItem("shouldStore", false); const prevExtent = JSON.parse(storeExtent); const prevLegendExpanded = JSON.parse(storeLegendExpanded); const prompt = document.getElementById("storagePrompt"); const promptContent = document.getElementById("storagePromptContent"); const remember = document.getElementById("rememberStorage"); const restore = document.getElementById("restore"); const saveStore = document.getElementById("storageSave"); const promptSession = () => { if (storeShowStore == "false") { remember.checked = true; } else { remember.checked = false; } if (storeShouldStore == "true") { restore.checked = true; promptContent.classList.add("persist"); } else { restore.checked = false; promptContent.classList.remove("persist"); } if (storeShowStore == "null") { storeShowStore = true; Storage.setItem("showStore", storeShowStore); } if (storeShowStore == "true") { prompt.classList.remove("hidden"); prompt.classList.remove("future"); } }; const restoreSession = async (view, expand) => { const future = prompt.classList.contains("future"); if (storeShouldStore == "true" && !future) { // Go to saved extent const center = prevExtent.center; const scale = prevExtent.scale; view.center = center; view.scale = scale; // Restore legend expand/collapse state expand.expanded = prevLegendExpanded; } prompt.classList.add("hidden"); }; remember.addEventListener("change", () => { storeShowStore = !remember.checked; Storage.setItem("showStore", storeShowStore); }); restore.addEventListener("change", () => { storeShouldStore = restore.checked; Storage.setItem("shouldStore", storeShouldStore); promptContent.classList.toggle("persist"); }); // ------------------------------------------ Variables ----------------------------------------- // Misc. variables const parcelsVisibleScale = 9_027; let userLang = "en"; let legendReady, extentReady, currentGraphic, currentTemplate; // Style variables const highlightColor = [0, 255, 255]; const parcelColor = [112, 61, 189]; const transparent = [0, 0, 0, 0]; const locationPinPath = "M215.7 499.2C267 435 384 279.4 384 192C384 86 298 0 192 0S0 86 0 192c0 87.4 117 243 168.3 307.2c12.3 15.3 35.1 15.3 47.4 0zM192 128a64 64 0 1 1 0 128 64 64 0 1 1 0-128z"; const white = [255, 255, 255]; const logoBlue = [0, 131, 191]; const logoSky = [198, 228, 248]; const logoGreen = [0, 136, 53]; const districtOutlineClr = white; const districtOutlineStyle = "short-dash-dot"; // Layer URLs const parcelsURL = "https://public.gis.lacounty.gov/public/rest/services/LACounty_Cache/LACounty_Parcel/MapServer/0"; const districtsURL = "https://services6.arcgis.com/mfr5GsRh8UYwsJgO/arcgis/rest/services/districts_dissolved/FeatureServer/10"; const countyURL = "https://services6.arcgis.com/mfr5GsRh8UYwsJgO/arcgis/rest/services/LACountyBoundary/FeatureServer/0"; const facilitiesParcelsURL = "https://services6.arcgis.com/mfr5GsRh8UYwsJgO/arcgis/rest/services/iPACS%20Facility%20Parcels/FeatureServer/0"; const facilitiesURL = "https://services6.arcgis.com/mfr5GsRh8UYwsJgO/arcgis/rest/services/iPACS%20Facilities/FeatureServer/0"; // API key esriConfig.apiKey = "AAPKa7be8a57e1c9454c8f71d6ec4ab1394dZhRfgW104inuLqvGCW1-O-HR1dALO11LE8oZJQOteNEMGRsVPdPT9c1HNAhjqC9-"; // ----------------------------------------- Tile Layers ---------------------------------------- const streetDistricts = new TileLayer({ url: "https://services.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer", legendEnabled: false, }); const streetMap = new TileLayer({ url: "https://services.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer", legendEnabled: false, }); const imageryDistricts = new TileLayer({ url: "https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer", legendEnabled: false, }); const imageryMap = new TileLayer({ url: "https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer", legendEnabled: false, }); // -------------------------------------- Create Map & View ------------------------------------- const map = new Map({ layers: [streetMap, imageryMap], basemap: storeBasemap, }); view = new MapView({ container: "viewDiv", map: map, color: transparent, haloColor: transparent, fillOpacity: 0, }); view.popup.maxInlineActions = 10; view.popup.dockOptions = { breakpoint: false, buttonEnabled: false }; // ---------------------------------------- Create Layers --------------------------------------- // LA County Boundary Layer const countyRenderer = { type: "simple", symbol: { type: "simple-fill", color: transparent, outline: { width: 0, }, }, }; const countyLyr = new FeatureLayer({ url: countyURL, outFields: [], legendEnabled: false, renderer: countyRenderer, }); map.add(countyLyr); // Districts Layer const districtsSSRenderer = { type: "simple", symbol: { type: "simple-fill", color: transparent, outline: { width: 1, color: districtOutlineClr, style: districtOutlineStyle, }, }, }; const districtsLSRenderer = { type: "simple", symbol: { type: "simple-fill", color: transparent, outline: { width: 2, color: districtOutlineClr, style: districtOutlineStyle, }, }, }; const districtsLyr = new FeatureLayer({ url: districtsURL, outFields: [], labelsVisible: false, blendMode: "destination-in", }); const districtQuery = { where: "OBJECTID IS NOT NULL", returnGeometry: true, }; const districtsOutline = new FeatureLayer({ url: districtsURL, outFields: [], labelsVisible: false, renderer: districtsSSRenderer, legendEnabled: true, title: "LACSD Service Area (gray shaded area is outside service area)", }); let districtGeom; districtsLyr.queryFeatures(districtQuery).then((results) => { if (results.features.length > 0) { districtGeom = results.features[0].geometry; } }); const districtsGroup = new GroupLayer({ layers: [streetDistricts, imageryDistricts, districtsLyr], legendEnabled: false, }); map.add(districtsGroup); // Parcels Layer const parcelsRenderer = { type: "simple", symbol: { type: "simple-fill", color: transparent, outline: { width: 1.5, color: parcelColor, }, }, }; const parcelsLyr = new FeatureLayer({ url: parcelsURL, outFields: "*", displayField: "APN", labelsVisible: false, minScale: parcelsVisibleScale, renderer: parcelsRenderer, title: "LA County Parcels", }); map.add(parcelsLyr); map.add(districtsOutline); // Parcel Labels const parcelLabels = new GraphicsLayer(); map.add(parcelLabels); // Facilities Layer const facilitiesClause = "SCORE >= 80"; const facilitiesSymbol = (color, outline = [255, 255, 255]) => { return { type: "simple-marker", path: facilityPath, size: 24, color: color, outline: { color: outline, width: 1.5, }, }; }; const facilitiesRenderer = { type: "simple", symbol: facilitiesSymbol([1224, 134, 49], (outline = [148, 72, 18])), }; facilityLocLyr = new FeatureLayer({ url: facilitiesURL, outFields: "*", displayField: "Address", legendEnabled: true, title: "Locations w/possible records", minScale: parcelsVisibleScale, definitionExpression: facilitiesClause, renderer: facilitiesRenderer, }); map.add(facilityLocLyr); // Highlight Layer highlightLyr = new GraphicsLayer(); const highlightPolySymbol = { type: "simple-fill", color: [0, 0, 0, 0], outline: { color: highlightColor, width: 3, }, }; const highlightPtSymbol = { type: "simple-marker", style: "circle", color: highlightColor, size: "10px", outline: { width: 0, }, }; map.add(highlightLyr); // Selection Layer selectLyr = new GraphicsLayer(); map.add(selectLyr); const selectSymbol = { type: "simple-fill", color: highlightColor.concat([0.3]), outline: { color: highlightColor, width: 2, }, }; filterFacilities = () => { if (favoritesLyr.graphics.length) { const oids = favoritesLyr.graphics.items .map((graphic) => graphic.attributes.ObjectId) .join(", "); const clause = `ObjectId NOT IN (${oids})`; facilityLocLyr.definitionExpression = `${facilitiesClause} AND ${clause}`; } else { facilityLocLyr.definitionExpression = facilitiesClause; } updateFavoriteCount(); }; // Favorites Layer favoritesLyr = new GraphicsLayer(); map.add(favoritesLyr); // ---------------------------------------- Create Popups ----------------------------------------- const facilityPopup = (feature) => { const src = feature.layer == facilityLocLyr ? starOutline : starSolid; const favoriteWord = src == starOutline ? "favorite" : "unfavorite"; const content = `The Districts may have relevant information about this location, depending on your information needs. Please submit a records request to have your specific data needs researched.
Click the below to ${favoriteWord} this location.
Click the to request records now.
`; return { title: feature.attributes.Address, content: content, actions: facilitiesActions, }; }; const parcelPopup = (feature, additionalContent = "") => { const content = `Click the button below to find more information about this parcel
. `; return { title: `Parcel ${feature.attributes.APN}`, content: additionalContent + content, actions: parcelActions, }; }; const miscPopup = (title) => { const miscContent = `No facility found with this address, but one may still exist for this location.
Click on nearby orange markers to view their addresses. One of them may be the location you are looking for, but with a different address format.
Not where you expected to be? Include the street direction if applicable (e.g., "123 N Main St")
`; return { title: title, content: miscContent, actions: miscActions, }; }; // ---------------------------------------- Create Widgets ---------------------------------------- // Home widget const home = new Home({ view: view, goToOverride: () => { view.goTo(districtsLyr.fullExtent); }, }); view.ui.add(home, "top-left"); // Requests widget const searches = new Expand({ view: view, content: document.getElementById("favorites-list-container"), collapseIconClass: "esri-icon-close", collapseTooltip: "Close", expandIconClass: "esri-icon-favorites", expandTooltip: "Favorites", expanded: false, mode: "floating", group: "top-right", }); // Legend widget const legend = new Legend({ view: view, }); const legendExpand = new Expand({ view: view, content: legend, collapseIconClass: "esri-icon-close", collapseTooltip: "Close", expandIconClass: "esri-icon-legend", expandTooltip: "Legend", expanded: false, mode: "floating", group: "top-right", }); view.ui.add([searches, legendExpand], "top-right"); // Search widget const pinSymbol = { type: "simple-marker", path: locationPinPath, color: [219, 78, 31], size: 24, outline: { width: 0, }, }; const supportsLocationEnabled = location.protocol === "https:"; const search = new Search({ view: view, autoselect: false, popupEnabled: false, resultGraphicEnabled: false, locationEnabled: supportsLocationEnabled, includeDefaultSources: false, allPlaceholder: "Address, place, or parcel...", }); const setSearching = () => { document.body.classList.add("searching"); search.container.querySelector( ".esri-search__submit-button" ).disabled = true; }; const unsetSearching = () => { document.body.classList.remove("searching"); search.container.querySelector( ".esri-search__submit-button" ).disabled = false; }; const suggestionsFromSource = async (sources, src) => { const results = { sourceIndex: sources.indexOf(src), source: src, }; params = { suggestTerm: search.searchTerm, maxSuggestions: src.maxResults, }; const suggestResults = await src.getSuggestions(params); results.results = suggestResults; return results; }; const fetchSuggestions = async () => { const sources = search.sources?.items; const searchSuggestions = []; for (const source of sources) { const results = await suggestionsFromSource(sources, source); searchSuggestions.push(results); } return searchSuggestions; }; const searchEnterOverride = async (e) => { if (e.key !== "Enter") return; if (document.body.classList.contains("searching")) { // Prevent search from starting e.preventDefault(); // Generate layer suggestions const searchSuggestions = await fetchSuggestions(); // Try to go to result from suggestions const [found, src] = await findSuggestedResult(searchSuggestions); // Else, resume search if (!found) { search.search().then(unsetSearching); } } }; view.ui.add(search, "top-right"); search.when(() => { const searchInput = document.getElementById(`${search.id}-input`); searchInput.addEventListener("keypress", searchEnterOverride); searchInput.addEventListener("keydown", searchEnterOverride); document.body.onkeydown = async (e) => { if (e.ctrlKey && e.key === "f") { e.preventDefault(); searchInput.select(); } }; }); countyLyr.when(() => { const searchSources = [ { layer: facilityLocLyr, searchFields: ["Address"], displayField: "Address", exactMatch: true, name: "Facilities", maxResults: 6, suggestionsEnabled: true, minSuggestCharacters: 2, }, { layer: parcelsLyr, searchFields: ["AIN", "APN"], displayField: "APN", exactMatch: true, outFields: ["AIN", "APN"], name: "Parcels", maxResults: 6, maxSuggestions: 6, suggestionsEnabled: true, minSuggestCharacters: 7, }, { url: "https://geocode-api.arcgis.com/arcgis/rest/services/World/GeocodeServer", singleLineFieldName: "SingleLine", outFields: ["Addr_type"], name: "Address", resultSymbol: pinSymbol, minSuggestCharacters: 4, filter: { geometry: countyLyr.fullExtent }, }, ]; search.sources = searchSources; }); const showNotFound = (searchText) => { document.getElementById("search-display").textContent = searchText; document.getElementById("not-found-modal").open = true; }; const featureFromSuggestion = async (lyr, field, value) => { const params = { where: `${field} = '${value}'`, outFields: "*", returnGeometry: true, }; const results = await lyr.queryFeatures(params); if (results.features.length) return results.features[0]; }; const findLayerResult = async (results) => { const result = results.find( (result) => result.results.length > 0 && result.source.layer ); if (!result) return [false, null]; const { feature } = result.results[0]; if (result.source.layer == facilityLocLyr) { await selectFacilities([feature]); return [true, result.source.name]; } if (result.source.layer == parcelsLyr) { await selectParcels([{ feature: feature }]); return [true, result.source.name]; } return [false, null]; }; const findSuggestedResult = async function (suggestions) { for (const suggestion of suggestions) { if (!(suggestion?.results?.length && suggestion?.source?.layer)) { continue; } const { layer, displayField, name } = suggestion.source; const { text } = suggestion.results[0]; const result = await featureFromSuggestion(layer, displayField, text); if (layer == facilityLocLyr) { await selectFacilities([result]); return [true, name]; } if (layer == parcelsLyr) { await selectParcels([{ feature: result }]); return [true, name]; } } return [false, null]; }; const customSearch2 = async (searchResults, suggestions) => { const { searchTerm, results } = searchResults; let log = true; let resultFound = false; let foundSource; try { const [foundLayerResult, sourceName] = await findLayerResult(results); if (foundLayerResult) { resultFound = true; foundSource = sourceName; return; } const [suggestedFound, suggestedName] = await findSuggestedResult( suggestions ); if (suggestedFound) { resultFound = true; foundSource = suggestedName; return; } const ain = searchTerm.replaceAll("-", ""); if (ain.match(/^[0-9]{10}$/)) { const currentAIN = await getActiveParcel(searchTerm); if (currentAIN) { const currentParcel = await featureFromSuggestion( parcelsLyr, "AIN", currentAIN ); if (currentParcel) { await selectParcels([ { feature: currentParcel, oldAIN: searchTerm }, ]); resultFound = true; foundSource = "Parcels"; return; } } } const result = results.find((result) => result.results.length > 0); if (result) { const { feature } = result.results[0]; await selectMisc(feature, searchTerm); resultFound = true; foundSource = "Address"; return; } const suggestAddress = suggestions.find( (suggestion) => suggestion.results.length > 0 ); if (suggestAddress) { const { text } = suggestAddress.results[0]; log = false; search.search(text); return; } showNotFound(searchTerm); } catch (e) { // If error, remove loader foundSource = e; showNotFound(searchTerm); } finally { if (log) { // Log user search logAction({ action: "search", searched_text: searchTerm, result_returned: resultFound, result_source: foundSource, }); } } }; const customSearch = async (results) => { let initialResultBool, initialResultSource, facilityFound, foundFacilities; const searchText = results.searchTerm; try { let result; for (const source of results.results) { if (source.results.length) { result = source.results[0]; initialResultSource = source.source.name; break; } } initialResultBool = result != undefined; // If facility located, return facility if (result && result.feature.layer == facilityLocLyr) { selectFacilities([result.feature]); return; } // If search is numeric, try to find facility by parcel, or if no parcel returned, current parcel const ain = results.searchTerm.replace(/[^0-9]/g, ""); if (!isNaN(ain)) { // Try to find facility by parcel const parcelFacilities = await getParcelFacilities(ain); if (parcelFacilities.length) { selectFacilities(parcelFacilities); return; } // If original result was parcel, return parcel if (result && result.feature.layer == parcelsLyr) { const graphic = result.feature; selectParcels([{ feature: graphic }]); return; } // Check if search term is old parcel, and if so, return current parcel const currentParcel = await getActiveParcel(results.searchTerm); if (currentParcel) { // Select current parcel from parcels layer const currentQuery = { where: `AIN = '${currentParcel}'`, outFields: "*", returnGeometry: true, }; const currentFeature = await parcelsLyr.queryFeatures(currentQuery); if (currentFeature.features.length) { const graphic = currentFeature.features[0]; const oldAin = results.searchTerm; selectParcels([{ feature: graphic, oldAIN: oldAin }]); return; } } } // If no facility/parcel found, return original result with popup if (result) { selectMisc(result.feature, result.name); return; } // If nothing found, hide loader and let user know nothing found showNotFound(results.searchTerm); } catch (e) { // If error, remove loader console.log(e); showNotFound(results.searchTerm); } finally { // Log user search logAction({ action: "search", searched_text: searchText, result_returned: initialResultBool, result_source: initialResultSource, facilities_returned: facilityFound, result_facilities: foundFacilities, }); } }; search.on("suggest-start", () => { suggestions.length = 0; setSearching(); }); search.on("suggest-complete", (e) => { for (const result of e.results) { suggestions.push(result); } unsetSearching(); }); search.on("search-clear", () => { unsetSearching(); }); search.on("search-blur", () => { unsetSearching(); }); search.on("search-complete", (results) => { if (view.popup.visible) { view.popup.close(); } if (suggestions.length) { customSearch2(results, [...suggestions]); } else { customSearch(results); } suggestions.length = 0; search.close(); }); // Basemap toggle widget const bmToggle = new BasemapToggle({ view: view, nextBasemap: storeBasemap == "satellite" ? "streets-navigation-vector" : "satellite", }); view.ui.add(bmToggle, "bottom-right"); bmToggle.when(() => { bmToggle.label = `Switch to ${bmToggle.nextBasemap.title}`; }); bmToggle.watch("activeBasemap", (active) => { Storage.setItem("basemap", active.id); bmToggle.label = `Switch to ${bmToggle.nextBasemap.title}`; const mapEffect = "grayscale(0.75) brightness(0.7)"; const districtEffect = "grayscale(0)"; if (active.id == "satellite") { streetMap.effect = "opacity(0%)"; streetDistricts.effect = "opacity(0%)"; imageryMap.effect = `opacity(100%) ${mapEffect}`; imageryDistricts.effect = `opacity(100%) ${districtEffect}`; } else { imageryMap.effect = "opacity(0%)"; imageryDistricts.effect = "opacity(0%)"; streetMap.effect = `opacity(100%) ${mapEffect}`; streetDistricts.effect = `opacity(100%) ${districtEffect}`; } }); // Last session widget const session = document.createElement("div"); session.className = "session-widget esri-widget esri-component"; session.title = "Session Settings"; const sessionBtn = document.createElement("button"); sessionBtn.type = "button"; sessionBtn.className = "esri-icon-settings esri-widget--button"; session.appendChild(sessionBtn); view.ui.add(session, "bottom-left"); sessionBtn.addEventListener("click", () => { prompt.classList.add("future"); prompt.classList.remove("hidden"); }); // Sketch (select) widget sketch = new Sketch({ activeTool: "rectangle", availableCreateTools: ["rectangle"], creationMode: "single", layer: selectLyr, snappingOptions: { enabled: false, }, tooltipOptions: { enabled: false, }, view: view, }); sketch.viewModel.polygonSymbol = selectSymbol; const select = document.createElement("div"); select.className = "select-widget esri-widget esri-component"; const selectBtn = document.createElement("button"); selectBtn.type = "button"; selectBtn.title = "Select from map"; selectBtn.id = "map-select-btn"; selectBtn.className = "esri-icon-cursor-marquee esri-widget--button"; select.appendChild(selectBtn); view.ui.add(select, "top-left"); selectBtn.addEventListener("click", () => { // Activate create rectangle sketch tool sketch.create("rectangle"); selectBtn.classList.add("active"); // Close any open popup or expanded widget view.popup.close(); for (const widget of [searches, legend]) { widget.expanded = false; } }); const selectionComplete = async (boxGraphic) => { highlightLyr.graphics = []; // When selection rectangle drawn, select faciliites const oids = favoritesLyr.graphics.items .map((graphic) => graphic.attributes.ObjectId) .join(", "); let where = facilityLocLyr.definitionExpression; if (oids) { where = `${where} AND ObjectId NOT IN (${oids})`; } const selectQuery = { where: where, geometry: boxGraphic.geometry, spatialRelationship: "intersects", outFields: "*", returnGeometry: true, }; const results = await facilityLocLyr.queryFeatures(selectQuery); selectLyr.graphics = []; const saveOIDs = new Array(); for (const fave of favoritesLyr.graphics) { saveOIDs.push(fave.attributes.ObjectId); } if (results.features.length) { for (const result of results.features) { if (!saveOIDs.includes(result.attributes.ObjectId)) { const resultGraphic = new Graphic(); resultGraphic.geometry = result.geometry; resultGraphic.attributes = result.attributes; // resultGraphic.symbol = highlightPtSymbol; resultGraphic.symbol = facilitiesSymbol(highlightColor); highlightLyr.graphics.add(resultGraphic); } } // Open confirmation modal const exceededStr = `Selection limit exceeded—first ${results.features.length.toLocaleString()} `; ctText = results.exceededTransferLimit ? exceededStr : results.features.length; document.getElementById("select-count").innerHTML = ctText; const facilityText = ctText == 1 ? "location" : "locations"; document.getElementById("facility-text").textContent = facilityText; selectModal.open = true; } }; sketch.on("create", (create) => { if (create.state == "complete") { selectionComplete(create.graphic); } if (["complete", "cancel"].includes(create.state)) { view.constraints.minZoom = -1; selectBtn.classList.remove("active"); } }); // -------------------------------------- Layout Adjustments -------------------------------------- // Move home widget inside zoom buttons view.ui.find("zoom").when(() => { zoomWidget = document.querySelector(".esri-component.esri-zoom"); homeWidget = document.querySelector(".esri-component.esri-home"); zoomOut = document.querySelector( ".esri-zoom .esri-icon-minus" ).parentElement; zoomWidget.appendChild(homeWidget); zoomWidget.appendChild(zoomOut); homeWidget.style.margin = 0; }); // --------------------------------------- Events Management -------------------------------------- // Close records request iframe on close button click document.querySelector("#iframe-container .close").onclick = () => { hideFrames(); Storage.setItem("request-addresses", ""); }; view.whenLayerView(districtsLyr).then(() => { // Limit map to Districts extent view .goTo(districtsLyr.fullExtent, { animate: false, duration: 0, }) .then(() => { extentReady = true; // Remove loader document.getElementById("loading-screen").hidden = true; // Prompt for "restore last session" when legend widget ready AND layers loaded if (legendReady) { promptSession(); if (storeShowStore == "false" && storeShouldStore == "true") { restoreSession(view, legendExpand); } saveStore.addEventListener("click", () => { restoreSession(view, legendExpand); }); } }); // Limit search results to Districts extent search.sources.items.forEach((src) => { if (src.name == "ArcGIS World Geocoding Service") { src.filter = { geometry: districtsLyr.fullExtent, }; } }); // Apply basemap mask const mapEffect = "grayscale(0.75) brightness(0.7)"; const districtEffect = "grayscale(0)"; if (map.basemap.id == "satellite") { streetMap.effect = "opacity(0%)"; streetDistricts.effect = "opacity(0%)"; imageryMap.effect = `opacity(100%) ${mapEffect}`; imageryDistricts.effect = `opacity(100%) ${districtEffect}`; } else { imageryMap.effect = "opacity(0%)"; imageryDistricts.effect = "opacity(0%)"; streetMap.effect = `opacity(100%) ${mapEffect}`; streetDistricts.effect = `opacity(100%) ${districtEffect}`; } }); // Save extent to local storage on extent change view.watch("extent", () => { storeExtent = JSON.stringify({ center: view.center, scale: view.scale, }); Storage.setItem("extent", storeExtent); if (view.scale < parcelsVisibleScale) { districtsOutline.renderer = districtsLSRenderer; } else { districtsOutline.renderer = districtsSSRenderer; } }); // Save legend expansion boolean to local storage on expand/collapse legendExpand.watch("expanded", (exp) => { storeLegendExpanded = exp; Storage.setItem("legendExpanded", exp); }); // Prompt for "restore last session" when legend widget ready AND layers loaded legendExpand.when(() => { legendReady = true; if (extentReady) { promptSession(); if (storeShowStore == "false" && storeShouldStore == "true") { restoreSession(view, legendExpand); } saveStore.addEventListener("click", () => { restoreSession(view, legendExpand); }); } }); view.watch("zoom", () => { if (sketch && sketch.state == "active") { view.constraints.minZoom = 16; } else { view.constraints.minZoom = -1; } const selectWidgetBtn = document.querySelector(".select-widget button"); if (!selectWidgetBtn) { return; } if (view.scale >= parcelsVisibleScale) { selectWidgetBtn.disabled = true; } else { selectWidgetBtn.disabled = false; } }); view.on("pointer-move", (evt) => { const opts = { include: [parcelsLyr, facilityLocLyr, favoritesLyr], }; view.hitTest(evt, opts).then((hovered) => { let cursor = "default"; if (hovered.results.length) { cursor = "pointer"; const parcelResults = new Array(); for (const result of hovered.results) { if (result.layer == parcelsLyr) { parcelResults.push(result); } } if (parcelResults.length) { labelParcel(parcelResults); } } else { parcelLabels.graphics = []; } document.getElementById("viewDiv").style.cursor = cursor; }); }); view.on("click", (evt) => { const opts = { include: [facilityLocLyr, favoritesLyr, parcelsLyr], }; view.hitTest(evt, opts).then((clicked) => { if (clicked.results.length) { const logData = { action: "feature click", }; const facilityFeatures = new Array(); const parcelFeatures = new Array(); for (const result of clicked.results) { if ([facilityLocLyr, favoritesLyr].includes(result.layer)) { facilityFeatures.push(result.graphic); } else if (result.layer == parcelsLyr) { parcelFeatures.push({ feature: result.graphic, }); } } if (facilityFeatures.length) { selectFacilities(facilityFeatures); logData.layer = facilityLocLyr.title; logData.n_features = facilityFeatures.length; } else if (parcelFeatures.length) { selectParcels(parcelFeatures); logData.layer = parcelsLyr.title; logData.n_features = parcelFeatures.length; } // Log feature click logAction(logData); } }); }); const labelAction = { id: "toggle-labels", className: "esri-icon-expand", title: "Toggle labels", }; const streetViewAction = { id: "street-view", className: "pegman", image: pegman, title: "Street view", }; const relatedFacilitiesAction = { id: "related-facilities", className: "esri-icon-map-pin", title: "Assoc. locations", visible: false, }; addToListAction = { id: "add-to-favorites", className: "favorite", image: starOutline, title: "Favorite", }; const praAction = { id: "records-request", className: "esri-icon-documentation", title: "Request records", }; const parcelDetailAction = { id: "parcel-details", className: "assessor esri-icon-review", title: "Parcel details", }; const updateLabelAction = () => { const swapClass = (current, desired) => { const elem = document.querySelector(`.${current}`); if (elem) { elem.classList.remove(current); elem.classList.add(desired); } }; if (showActionLabels) { view.popup.container.classList.add("show-labels"); labelAction.className = "esri-icon-expand"; swapClass("esri-icon-collapse", "esri-icon-expand"); } else { view.popup.container.classList.remove("show-labels"); labelAction.className = "esri-icon-collapse"; swapClass("esri-icon-expand", "esri-icon-collapse"); } view.popup.render(); }; view.popup.watch("visible", (visible) => { if (visible) { view.popup.collapseEnabled = false; updateLabelAction(); } else { highlightLyr.graphics = []; currentGraphic = null; currentTemplate = null; search.clear(); } }); view.popup.watch("selectedFeature", async (graphic) => { if (graphic) { view.popup.container.classList.add("loading"); view.popup.container.classList.remove("show-related"); // When popup includes multiple features, place popup over // selected feature rather than at center of feature locations const location = graphic.geometry.type == "point" ? graphic.geometry : findLabelPt(graphic.geometry); view.popup.location = location; // Conditionally show assoc. locations feature action only if parcel has related facilities const popupActions = view.popup.selectedFeature.popupTemplate.actions; const relatedAction = popupActions.items.find( (action) => action.id == "related-facilities" ); if (relatedAction) { const related = await getParcelFacilities(graphic.attributes.AIN); if (related.length > 0) { relatedAction.visible = true; view.popup.container.classList.add("show-related"); } else { relatedAction.visible = false; } } // Present correct favorite icon based on feature "favorite" status if ([facilityLocLyr, favoritesLyr].includes(graphic.layer)) { const favorite = document.getElementById( `oid-${graphic.attributes.ObjectId}` ); if (favorite != null) { favoriteOn(); } else { favoriteOff(); } } view.popup.container.classList.remove("loading"); } }); view.popup.on("trigger-action", (evt) => { const feature = view.popup.selectedFeature; const popupLayer = feature.layer ? feature.layer : facilityLocLyr; const popupFeature = feature.attributes[popupLayer.displayField]; const { AIN } = feature.attributes; const logData = { action: "popup action click", popup_action: evt.action.id, popup_layer: popupLayer.title, popup_feature: popupFeature, }; switch (evt.action.id) { case labelAction.id: showActionLabels = !showActionLabels; updateLabelAction(); break; case streetViewAction.id: const loc = feature.geometry.type == "point" ? feature.geometry : findLabelPt(feature.geometry); const lat = loc.latitude; const lng = loc.longitude; const streetviewBase = document.getElementById("street-view-url").href; const streetviewUrl = `${streetviewBase}?lat=${lat}&lng=${lng}&language=${userLang}&title=${encodeURIComponent( view.popup.title )}`; showFrame("street-view-frame", streetviewUrl); break; case parcelDetailAction.id: const assessorURL = "https://portal.assessor.lacounty.gov/parceldetail"; window.open(`${assessorURL}/${AIN}`, "_blank"); break; case relatedFacilitiesAction.id: getParcelFacilities(AIN).then((facilities) => { if (facilities.length) { selectFacilities(facilities); } }); break; case addToListAction.id: const { ObjectId } = feature.attributes; const existing = document.getElementById(`oid-${ObjectId}`); if (existing) { // Remove from favorites favoriteOff(); removeFavorite(ObjectId); logData.popup_action = "remove-from-favorites"; } else { favoriteOn(); addFavorite(feature); } break; case praAction.id: updateRequests([view.popup.selectedFeature]).then(() => { const { href } = document.getElementById("records-request-url"); showFrame("records-request-frame", href); }); break; } // Log popup action trigger logAction(logData); }); const facilitiesActions = [ labelAction, addToListAction, praAction, streetViewAction, ]; const parcelActions = [ labelAction, relatedFacilitiesAction, parcelDetailAction, streetViewAction, ]; const miscActions = [labelAction, streetViewAction]; updateRequests = async (features) => { const addresses = features.map( (feature) => `'${feature.attributes.Address}'` ); const queryJSON = { where: `Address IN (${addresses.join(", ")})`, outFields: ["Address", "FACILITY_ID", "CAT1", "CAT2"], }; const results = await query.executeQueryJSON(facilitiesURL, queryJSON); const requestAddresses = new Object(); if (results.features.length) { for (const result of results.features) { const { FACILITY_ID: fid, CAT1: cat1, CAT2: cat2, Address: address, } = result.attributes; const facility = { fid, cat1, cat2 }; if (Object.keys(requestAddresses).includes(address)) { requestAddresses[address].push(facility); } else { requestAddresses[address] = [facility]; } } } Storage.setItem("request-addresses", JSON.stringify(requestAddresses)); }; function findLabelPt(geom) { if (geom.type == "point") { return geom; } x_arr = [ 0, 0, 0, -1, 1, -1, 1, -1, 1, 0, 0, -2, 2, -1, 1, -1, 1, -2, 2, -2, 2, -2, 2, -2, 2, 0, 0, -3, 3, -1, 1, -1, 1, -3, 3, -3, 3, -2, 2, -2, 2, -3, 3, -3, 3, -3, 3, -3, 3, ]; y_arr = [ 0, 1, -1, 0, 0, 1, 1, -1, -1, 2, -2, 0, 0, 2, 2, -2, -2, 1, 1, -1, -1, 2, 2, -2, -2, 3, -3, 0, 0, 3, 3, -3, -3, 1, 1, -1, -1, 3, 3, -3, -3, 2, 2, -2, -2, 3, 3, -3, -3, ]; const geomExt = geom.extent.clone(); const viewExt = view.extent.clone(); const xmin = Math.max(geomExt.xmin, viewExt.xmin); const xmax = Math.min(geomExt.xmax, viewExt.xmax); const ymin = Math.max(geomExt.ymin, viewExt.ymin); const ymax = Math.min(geomExt.ymax, viewExt.ymax); const center = Point.fromJSON({ x: (xmin + xmax) / 2, y: (ymin + ymax) / 2, spatialReference: geom.spatialReference, }); let pt = center; const steps = 7; const x_step = Math.abs(xmax - xmin) / steps; const y_step = Math.abs(ymax - ymin) / steps; const hasPt = () => { return geom.contains(pt); }; for (let i = 0; i < x_arr.length; i++) { if (hasPt()) { return pt; } pt.x = center.x + x_arr[i] * x_step; pt.y = center.y + y_arr[i] * y_step; } return null; } const labelParcel = (results) => { const mapPt = results[0].mapPoint; parcelLabels.graphics = []; const resultAPNs = []; let location; let labelPt; results.forEach((feat) => { const ext = feat.graphic.geometry.extent; const loc = `${ext.xmin};${ext.xmax};${ext.ymin};${ext.ymax}`; const featAPN = feat.graphic.attributes.APN; if (!location) { location = loc; labelPt = findLabelPt(feat.graphic.geometry); if (!labelPt) { labelPt = mapPt; } } if (location == loc) { resultAPNs.push(featAPN); } }); const resultText = results.length < 4 ? resultAPNs.join("\n") : resultAPNs.slice(0, 3).join("\n") + "\n. . ."; const text = { type: "text", color: parcelColor, haloColor: "white", haloSize: 1, text: resultText, font: { size: 14, weight: "bold", }, }; parcelLabels.graphics.add( new Graphic({ geometry: labelPt, symbol: text, }) ); }; // ------------------------------------------ Responsive ------------------------------------------ const viewDiv = document.getElementById("viewDiv"); const viewClasses = [ "esri-view", "esri-view-width-medium", "esri-view-width-greater-than-xsmall", "esri-view-width-greater-than-small", "esri-view-width-less-than-large", "esri-view-width-less-than-xlarge", "esri-view-height-medium", "esri-view-height-greater-than-xsmall", "esri-view-height-greater-than-small", "esri-view-height-less-than-large", "esri-view-height-less-than-xlarge", "esri-view-orientation-landscape", ]; const appHeight = () => { let windowHeight = document.documentElement.clientHeight; const topbar = document.getElementById("topbar"); if (topbar) { windowHeight = windowHeight - topbar.scrollHeight; } const translateBar = document.querySelector("iframe[role='navigation']"); if (translateBar) { windowHeight = windowHeight - translateBar.scrollHeight; } document.documentElement.style.setProperty( "--app-height", `${windowHeight}px` ); }; window.onresize = appHeight; const windowChange = () => { viewDiv.className = viewClasses.join(" "); appHeight(); }; windowChange(); view.on("resize", () => { windowChange(); }); });