import { getLayers } from "./coz-helpers.js";
import { getQuery } from "./coz-helpers.js";
import { hasLayer } from "./coz-helpers.js";
// import {compile} from "handlebars";
// TODO ACCOUNT FOR GEOJSON TYPE
// TODO MAKE SEPARATE HIGHLIGHT AND CLICK FUNCTIONS
var highlightState = {
click: {
id: 0,
layer: "",
source: "",
sourceLayer: "",
},
highlight: {
id: 0,
layer: "",
source: "",
sourceLayer: "",
},
};
/**
*
* @param {*} map
* @param {*} feature
* @param {*} highlightOnly
*/
function highlightAddFeature(map, feature, highlightOnly) {
// console.log(feature)
var highlightMap = map;
var highlightType = !highlightOnly ? "click" : "highlight";
var highlightStateType = {
click: {
click: true,
},
highlight: {
highlight: true,
},
};
if (!feature.id && feature.id != 0) {
console.warn("no feature id, highlight error");
return;
}
highlightClearFeature(highlightMap, highlightType);
var clone = [feature.layer].slice();
var highlightLayer = clone[0];
var height =
highlightLayer.type === "fill-extrusion" ? highlightLayer.paint["fill-extrusion-height"] : 0;
highlightLayer.id = "cozHighlight_" + highlightLayer.id.replace("cozHighlight_", "");
highlightLayer.type =
highlightLayer.type === "fill" || highlightLayer.type === "line"
? "line"
: highlightLayer.type === "fill-extrusion"
? "fill"
: "circle";
highlightLayer.paint = !height
? highlightLayerPaint(highlightLayer.type)
: highlightLayerPaint(highlightLayer.type, highlightLayer.paint["fill-extrusion-color"]);
if (highlightLayer.type === "fill-extrusion") {
highlightLayer.paint["fill-extrusion-height"] = height;
}
highlightLayer.layout = {
visibility: "none",
};
if (!hasLayer(highlightMap, highlightLayer.id)) {
console.log("adding highlight layer", highlightLayer.id);
map.addLayer(highlightLayer);
}
highlightState[highlightType].id = feature.id;
highlightState[highlightType].layer = highlightLayer.id;
highlightState[highlightType].source = highlightLayer.source;
highlightState[highlightType].sourceLayer = highlightLayer["source-layer"];
map.setFeatureState(
{ source: highlightLayer.source, sourceLayer: highlightLayer["source-layer"], id: feature.id },
highlightStateType[highlightType]
);
map.setLayoutProperty(highlightLayer.id, "visibility", "visible");
}
/**
*
* @param {*} e
*/
function highlightClearFeature(e, type, noFeatures) {
var type = !type ? "click" : type;
if (noFeatures) {
//GET RID OF POPUP WINDOW ON CLICK HIGHLIGHT
if (document.getElementById("map--info-window")) {
document.getElementById("map--info-window").style.display = "none";
}
}
var layers = getLayers(e);
if (layers.indexOf(highlightState[type].layer) > -1) {
if (type === "highlight") {
e.setFeatureState(
{
source: highlightState[type].source,
sourceLayer: highlightState[type].sourceLayer,
id: highlightState[type].id,
},
{ highlight: false }
);
var click = e.getFeatureState({
source: highlightState[type].source,
sourceLayer: highlightState[type].sourceLayer,
id: highlightState[type].id,
});
if (!click.click) e.setLayoutProperty(highlightState[type].layer, "visibility", "none");
} else {
e.setFeatureState(
{
source: highlightState[type].source,
sourceLayer: highlightState[type].sourceLayer,
id: highlightState[type].id,
},
{ click: false }
);
e.setLayoutProperty(highlightState[type].layer, "visibility", "none");
//GET RID OF POPUP WINDOW ON CLICK HIGHLIGHT
if (document.getElementById("map--info-window")) {
document.getElementById("map--info-window").style.display = "none";
}
}
}
}
/**
*
* @param {*} type
*/
function highlightLayerPaint(featureType, color) {
if (color) {
color =
"rgba(" + color.r * 1000 + "," + color.g * 1000 + "," + color.b * 1000 + "," + 0.5 + ")";
}
var paint = {
line: {
"line-color": "yellow",
"line-width": [
"case",
["==", ["feature-state", "click"], true],
6,
["==", ["feature-state", "highlight"], true],
2,
0,
],
"line-opacity": [
"case",
["==", ["feature-state", "click"], true],
1,
["==", ["feature-state", "highlight"], true],
0.7,
0,
],
},
circle: {
"circle-radius": 14,
"circle-color": "transparent",
"circle-stroke-color": "yellow",
"circle-stroke-width": 4,
"circle-opacity": [
"case",
["==", ["feature-state", "click"], true],
1,
["==", ["feature-state", "highlight"], true],
0.7,
0,
],
"circle-stroke-opacity": [
"case",
["==", ["feature-state", "click"], true],
1,
["==", ["feature-state", "highlight"], true],
0.7,
0,
],
},
fill: {
"fill-color": [
"case",
["==", ["feature-state", "click"], true],
"yellow",
["==", ["feature-state", "highlight"], true],
"yellow",
"transparent",
],
"fill-opacity": 0.7,
},
"fill-extrusion": {
"fill-extrusion-color": [
"case",
["==", ["feature-state", "click"], true],
"yellow",
["==", ["feature-state", "highlight"], true],
"yellow",
color,
],
"fill-extrusion-opacity": 0.6,
},
};
return paint[featureType];
}
/**
*
* @memberof cozMap
* @method getFeatures
* @param {*} map
* @param {*} e
* @param {*} l
*/
async function getFeatures(map, e, l) {
var layers = map.getStyle().layers;
if (layers.indexOf("gl-draw-polygon-stroke-inactive.cold") > -1) {
if (
map.getPaintProperty("gl-draw-polygon-stroke-inactive.cold", "line-color") &&
map.getPaintProperty("gl-draw-polygon-stroke-inactive.cold", "line-color") != "red"
) {
return [];
}
}
//SHOULD MAYBE MOVE THIS OUT OF HERE TO A HELPER FILE
var point;
if (e.point && e.lngLat) {
point = e.point;
}
if (!e.point && e.lngLat) {
point = map.project(e.lngLat);
}
if (!e.point && !e.lngLat) {
point = map.project(e.coordinates);
}
var bboxClickTargetSize = map.getZoom() * 1.5 < 18 ? 18 : map.getZoom() * 1.5;
var bbox = [
[point.x - bboxClickTargetSize / 2, point.y - bboxClickTargetSize / 2],
[point.x + bboxClickTargetSize / 2, point.y + bboxClickTargetSize / 2],
];
var bboxCoords = [map.unproject(bbox[0]), map.unproject(bbox[1])];
var getFeatureOpts = getQuery(window.location.search.substring(1));
if (getFeatureOpts && getFeatureOpts.debug === "true") {
var poly = [
[bboxCoords[0].lng, bboxCoords[0].lat],
[bboxCoords[1].lng, bboxCoords[0].lat],
[bboxCoords[1].lng, bboxCoords[1].lat],
[bboxCoords[0].lng, bboxCoords[1].lat],
[bboxCoords[0].lng, bboxCoords[0].lat],
];
if (!map.getSource("clicked-bbox")) {
map.addSource("clicked-bbox", {
type: "geojson",
data: {
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [poly],
},
},
});
map.addLayer({
id: "clicked-bbox",
type: "fill",
source: "clicked-bbox",
layout: {},
paint: {
"fill-color": "firebrick",
"fill-opacity": 0.5,
},
});
} else {
map.getSource("clicked-bbox").setData({
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [poly],
},
});
}
}
var queriedFeatures, bboxFeatures;
if (l && l.length > 0) {
queriedFeatures = map.queryRenderedFeatures(point, {
layers: l,
});
bboxFeatures = map.queryRenderedFeatures(bbox, {
layers: l,
});
} else {
queriedFeatures = map.queryRenderedFeatures(point);
bboxFeatures = map.queryRenderedFeatures(bbox);
}
const combinedFeatures = [...queriedFeatures, ...bboxFeatures];
const features = cleanFeatures(combinedFeatures);
if (features.length > 0) {
for (let i = 0; i < features.length; i++) {
const obj = features[i];
try {
const queryUrl = `https://cozmaps.org/features/functions/postgisftw.get_attachments/items.json?schema_name=public&table_name=${obj.sourceLayer?.replace(
"public.",
""
)}&parent_id=${obj.id}`;
console.log({ queryUrl });
const attachments = await fetch(queryUrl).then((res) => res.json());
console.log({ attachments });
if (attachments && attachments.length > 0) {
attachments.forEach((a, i) => {
obj.properties[`attachment_img_${i}`] = a?.url;
//TODO add an image gallery to the popup if more than 1 image is found
obj.properties[`attachment_img_name_${i}`] = a?.name;
});
}
} catch (error) {
console.error("Error fetching attachments:", error);
}
}
}
if (getFeatureOpts && getFeatureOpts.debug === "true") {
console.log({ combinedFeatures });
console.log({ features });
}
function cleanFeatures(objects) {
const features = [];
const props = [];
for (let i = 0; i < objects.length; i++) {
const obj = objects[i];
obj.properties["__vt__id__index"] = obj.id;
const p = JSON.stringify(obj.properties);
let unique = true;
props.forEach((e) => {
if (p === e) {
unique = false;
}
});
if (unique) props.push(p);
if (
obj &&
unique &&
obj.source != "composite" &&
obj.layer.metadata &&
obj.layer.metadata.popup
) {
features.push(obj);
}
}
return features;
}
if (!features) {
return;
}
//SORT FEATURE PROPERTIES BY FIELD NAME
features.forEach(function (f) {
var props = {};
Object.keys(f.properties)
.sort()
.forEach(function (key) {
props[key] = f.properties[key];
});
f.properties = props;
});
return features;
}
function popup(map, features, n, highlightOnly) {
map.once("contextmenu", function () {
highlightClearFeature(popupMap, highlightType);
if (document.getElementById("map--info-window-close")) {
document.getElementById("map--info-window-close").click();
}
});
var popupMap = map;
var highlightType = !highlightOnly ? "click" : "highlight";
if (!features || features.length === 0) {
highlightClearFeature(popupMap, highlightType, true);
return;
}
var featuresTemp = features.filter((f) => {
return f != null;
});
var popupFeatures = featuresTemp;
if (!popupFeatures.length === 0 && !highlightOnly) {
highlightClearFeature(popupMap, "click");
return;
}
if (highlightOnly && popupFeatures.length > 0) {
highlightAddFeature(map, features[0], true);
return;
}
var x = !n ? 0 : n;
highlightAddFeature(popupMap, popupFeatures[x]);
var popupDiv = document.createElement("div");
// GET TITLE OR USE FEATURE PROPERTIES
var popupHeading = "Feature Properties";
for (var v in popupFeatures[x].properties) {
if (
(v.toUpperCase() === "NAME" || v.toUpperCase() === "TITLE") &&
popupFeatures[x].properties[v] &&
popupFeatures[x].properties[v] != null &&
popupFeatures[x].properties[v] != "null"
) {
popupHeading = popupFeatures[x].properties[v]; //.substring(0,20).trim();
// console.log(popupFeatures[x].properties[v] )
//if ((popupFeatures[x].properties[v]).length > 20) popupHeading += "..."
}
}
if (
popupHeading === "Feature Properties" &&
popupFeatures[x].layer.metadata &&
popupFeatures[x].layer.metadata.name
) {
popupHeading = popupFeatures[x].layer.metadata.name; //.substring(0,20).trim()
}
var popupDivTitle = popupShowMoreFeatures(popupMap, popupFeatures, popupHeading, x);
popupDiv.appendChild(popupDivTitle);
var popupHtml = popupBuildHtml(map, popupFeatures[x]);
popupDiv.appendChild(popupHtml);
/**
* SHOULD MOVE THIS TO CREATING THE DOM ELEMENT IN THE JS FILE INSTEAD OF IN THE HTML WOULD NEED TO HAVE A STYLESHEET TO GO ALONG WITH IT
*/
var popupWindow = document.getElementById("map--info-window-content");
if (popupWindow.children.length > 0) popupWindow.removeChild(popupWindow.childNodes[0]);
popupWindow.appendChild(popupDiv);
document.getElementById("map--info-window").style.display = "block";
if (window.innerWidth > 768) {
// var width = document.querySelector("#map--info-window-content").children[0].children[1].offsetWidth
// console.log(document.querySelector("#map--info-window-content").children[0].children[1], width)
document.getElementById("map--info-window").style.width = "300px";
} else {
document.getElementById("map--info-window").style.width = "100%";
}
}
function popupShowMoreFeatures(map, features, heading, x) {
var newMap = map;
var div = document.createElement("div");
div.style.textAlign = "center";
div.style.height = "auto";
div.style.padding = "0 0 10px 0";
div.style.borderBottom = "solid thin lightgray";
var prev = document.createElement("button");
prev.classList = "btn btn-link btn-sm";
prev.innerHTML = "◀";
prev.style.marginLeft = "10px";
prev.style.float = "left";
prev.onclick = function () {
popup(newMap, features, x - 1);
};
var next = document.createElement("button");
next.classList = "btn btn-link btn-sm";
next.style.marginRight = "10px";
next.innerHTML = "▶";
next.style.float = "right";
next.style.position = "absolute";
next.style.top = "10px";
next.style.right = "20px";
next.onclick = function () {
popup(newMap, features, x + 1);
};
if (x == features.length - 1) {
next.style.opacity = "0";
next.style.visibility = "hidden";
next.style.cursor = "default";
}
if (x == 0) {
prev.style.opacity = "0";
prev.style.visibility = "hidden";
prev.style.cursor = "default";
}
div.appendChild(prev);
var span = document.createElement("div");
span.textContent = heading;
span.style.fontSize = "18px";
span.style.lineHeight = "28px";
span.style.fontWeight = "600";
span.style.margin = "0 48px";
div.appendChild(span);
div.appendChild(next);
return div;
}
function popupBuildHtml(map, f) {
var popupDiv = document.createElement("div");
var popupTemplate = !f.layer.metadata
? true
: f.layer.metadata.popup && f.layer.metadata.popup === false
? false
: f.layer.metadata.popup === true
? []
: f.layer.metadata.popup;
if (popupTemplate || popupTemplate === undefined) {
var table = document.createElement("table");
table.classList = "table table-striped";
// table.style.width = "auto";
table.style.minWidth = "280px";
var tbody = document.createElement("tbody");
var keys = Object.keys(f.properties);
const aliasFields = {
// "AADT": "Average Annual Daily Traffic",
// "PARCELNUM": "Parcel ID",
// "Land_Use": "Land Use",
// "MASTER_TNM": "Tax Name",
// "FIRST_OWNE": "Owner",
// "ERU": "ERU for Account",
// "Sump_Depth": "Sump Depth",
// "Owner_1": "Owner",
// "Legal_Description": "Legal Description",
// "ELEV": "Elevation",
// "SQFT": "Impervious Surface",
// "FIELDID": "Field ID",
// "url_pdf": "URL for Inpsection Report",
// "IDUP": "Upstream Asset ID",
// "IDDN": "Downstream Asset ID",
// "INSPECTLOG": "Inspection Log",
// "audlink": "Auditor Link",
CreationDate: "Creation Date",
EditDate: "Edit Date",
url_1: "Image 1",
url_2: "Image 2",
url_3: "Image 3",
url_4: "Image 4",
esrignss_positionsourcetype: "Position Source Type",
};
const excludeFields = [
"ACCOUNT_NUM",
"CENTROID_X",
"CENTROID_Y",
"Creator",
"DATEUPDATE",
"END_X",
"END_Y",
"ERU_CHARGE",
"Editor",
"Enabled",
"FID",
"FIRST_NOTE",
"GEO_ID",
"GlobalID",
"IKey",
"INSIDE_X",
"INSIDE_Y",
"Land_Use_Name",
"Log_Date",
"MID_X",
"MID_Y",
"OBJECTID",
"OID",
"OLDFIELDID",
"SHAPE_Area",
"SHAPE_Leng",
"SHAPE_Length",
"START_X",
"START_Y",
"Shape_Area",
"Shape_Leng",
"Shape_Leng",
"Shape_Length",
"Shape_Length",
"TEMP",
"acres_1",
"ca",
"color",
"dt_created",
"dt_edited",
"dt_updated",
"id",
"id",
"l_bearing",
"obs_pk",
"pano",
"point_y",
"point_x",
"shape_leng",
"skey",
"userkey",
"vtlid",
"xcoord",
"ycoord",
"extrudeheight",
"parcel_number",
"uuid",
"plan_local",
"__vt__id__index",
];
excludeFields.map(function (f, i) {
excludeFields[i] = f.toUpperCase();
});
//GET POPUP VALUES FROM LAYER METADATA IF THEY EXIST
var layers = map.getStyle().layers;
var layer = layers.filter((l) => {
return l.id === f.layer.id.replace("cozHighlight_", "");
});
var popupValues = !layer[0].metadata
? null
: !layer[0].metadata.popup
? null
: layer[0].metadata.popup;
if (popupValues && popupValues != true) {
// console.log(popupValues.length)
keys = keys.filter((k) => {
return popupValues.indexOf(k) > -1;
});
}
var hasImage = false;
table.innerHTML += `<!-- ${f.layer.id} -->`;
keys.forEach(function (k, i) {
const key = aliasFields[k] ? aliasFields[k] : k.replace(/esrignss/g, "gps_");
var val = f.properties[k];
if (excludeFields.indexOf(k.toUpperCase()) < 0) {
if (val && val != null && val != "" && val != " " && val != undefined && val != "null") {
var property = isMapillary(val, f)
? formatMapillary(val)
: isEpochDate(val)
? formatDate(val)
: isLink(val)
? formatLink(val)
: isParcel(val)
? formatParcel(val)
: isNumber(val)
? formatNumber(val)
: val;
tbody.innerHTML += `<tr><td>${formatTitle(key)}</td><td>${property}</td></tr>`;
}
if (!hasImage) hasImage = isImage(k, val);
}
});
if (hasImage != false) {
var popupImageLink = document.createElement("a");
popupImageLink.href = hasImage;
popupImageLink.target = "_blank";
var popupImg = document.createElement("img");
popupImg.src = hasImage;
popupImg.style.width = "100%";
popupImageLink.appendChild(popupImg);
popupDiv.appendChild(popupImageLink);
}
table.appendChild(tbody);
popupDiv.appendChild(table);
}
return popupDiv;
}
export { getFeatures, popup, highlightAddFeature, highlightClearFeature };
/****
* HELPERS
*/
function isImage(key, value) {
if (!value) return false;
if (key === "alt_link") return false;
if (value.toString().indexOf("https") != 0) return false;
if (value.split(",")) {
value = value.split(",")[0];
}
let img = value.split(".");
let images = ["JPG", "JPEG", "PNG"];
console.log(images.indexOf(img[img.length - 1].toUpperCase()));
if (images.indexOf(img[img.length - 1].toUpperCase()) < 0) {
return false;
} else {
return value;
}
}
function formatTitle(str) {
return str.replace(/_/g, " ");
}
function isMapillary(value, feature) {
return value === "key" && feature.source === "mapillary";
}
function formatMapillary(value) {
return `<img src='https://d1cuyjsrcm0gby.cloudfront.net/${value}/thumb-320.jpg'>`;
}
function isEpochDate(value) {
var simpleDateRegex = new RegExp(/^\d{13}$/);
return simpleDateRegex.test(value);
}
function formatDate(value) {
var valueDate = new Date(value);
return valueDate.toLocaleDateString();
}
function isLink(value) {
var urlRegExCheck = new RegExp(
/[-a-zA-Z0-9@:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9@:%_\+.~#?&//=]*)?/gi
);
return urlRegExCheck.test(value);
}
function formatLink(value) {
if (value.split(",").length > 1) {
value = value.split(",")[0];
}
return `<a href="${value}" target='_blank'><i class='fas fa-external-link-alt'></i> View Resource</a></td></tr>`;
}
function isParcel(value) {
var parcelRegex = new RegExp(/^\d{2}[-]\d{2}[-]\d{2}[-]\d{2}[-]\d{3}$/);
return value == "PARCELNUM" || parcelRegex.test(value);
}
function formatParcel(value) {
return `<a href='https://muskingumoh-auditor-classic.ddti.net/Data.aspx?ParcelID=${value}' target='_blank' class='text-center'><i class='fas fa-external-link-alt'></i> ${value}</a>`;
}
function isNumber(value) {
var isNumber = /^\d*\.?\d*$/;
var isYear = /^[12][0-9]{3}$/;
return isNumber.test(value) && !isYear.test(value);
}
function formatNumber(value) {
return Number(value).toLocaleString();
}
/**
* genId
* Generate an ID of x length, NOT IN USE
* @param {Number} length
*/
function genId(length) {
var result = "";
var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var charactersLength = characters.length;
for (var i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}