diff --git a/web/mapComparison-body.jsp b/web/mapComparison-body.jsp
index f7cca2fe..ba1bc046 100755
--- a/web/mapComparison-body.jsp
+++ b/web/mapComparison-body.jsp
@@ -96,7 +96,7 @@
//AntwebUtil.log("mapComparison-body.jsp taxon:" + thisChild);
innerloop = 0;
- while (innerloop < 3) {
+ while (innerloop < 1) {
//AntwebUtil.log("mapComparison-body.jsp taxon:" + thisChild + " innerloop:" + innerloop);
diff --git a/web/maps/googleMapInclude.jsp b/web/maps/googleMapInclude.jsp
index 282fc4c5..4a49991d 100644
--- a/web/maps/googleMapInclude.jsp
+++ b/web/maps/googleMapInclude.jsp
@@ -1,7 +1,3 @@
-
-
-
-
<%@ page import="org.calacademy.antweb.Map" %>
<%@ page import="org.calacademy.antweb.util.UserAgentTracker" %>
@@ -9,154 +5,87 @@
boolean displayMap = true;
if (HttpUtil.isBot(request)) {
- out.println("Log In to see maps.");
- displayMap = false;
- } %>
-
-<%
-/*
- Here we generate the actual map.
-
-Includes /maps/googleMapPreInclude.jsp and /maps/googleMapInclude.jsp
- dynamicMap-body.jsp
- specimen-body.jsp
- group-body.jsp:
- collection-body.jsp:
- taxonPage-body.jsp:
- locality-body.jsp:
- search/fieldGuide-body.jsp:
- mapComparison-body.jsp
- overview-body.jsp
-
-googleMapPreInclude.jsp
- script /maps/drawGoogleMap.js
-
-googleMapInclude.jsp
- calls.push("< %= googleMapFunction % >");
-
-// V2?
-includeMap.jsp
- Not used? Yes it is. In the edit geolocale curator page and for localityOverviews that don't have a map but do have a centroid.
- Probably cases that should be eliminated.
- include /maps/googleMap.jsp
-
-googleMap.jsp
- google.maps.event.addDomListener(window, 'load', makeMap)
-
-map.jsp
- not used? Testing. http://localhost/antweb/maps/map.jsp
- google.maps.event.addDomListener(window, 'load', initialize);
-
-map.jsp, googleMap.jsp and includeMap.jsp to be removed once the above is resolved (see incudeMap.jsp).
-*/
+ out.println("Log In to see maps.");
+ displayMap = false;
+ }
%>
-
<%
- //A.log("googleMapInclude.jsp map:" + map + " displayMap:" + displayMap);
-
- // We assume that map, object{"specimen", "taxon", "locality", "collection"}, mapType, and objectName are already set
- if (map != null && displayMap) {
-
- String googleMapFunction = map.getGoogleMapFunction();
-
- if ((googleMapFunction != null) && (googleMapFunction.length() > 0)) {
-
- //A.log("googleMapInclude.jsp object:" + object + " objectName:" + objectName + " mapType:" + mapType);
- //AntwebUtil.logStackTrace();
- Map.addToDisplayMapCount();
- Map.addToDisplayMapCount(mapType);
-
- //String divName = map.getMapName();
- int n = googleMapFunction.indexOf("', '") + 4;
- int o = googleMapFunction.indexOf("'", n);
- String divName = googleMapFunction.substring(n, o);
- String mapSize = "small";
- if (object.equals("dynamic")) {
- mapSize = "large";
- googleMapFunction = googleMapFunction.replaceFirst("small","big");
- }
-
- if ("large".equals(mapSize)) {
- if (map.getSubtitle() != null) out.println("
" + map.getSubtitle() + "
");
- }
-
- //A.log("googleMapInclude.jsp divName:" + divName + " googleMapFunction:" + googleMapFunction + " object:" + object);
- // String heightAndWidth = (!object.equals("dynamic")) ? "height:232px;width:232px;" : "height:400px;width:926px;";
- String heightAndWidth = new String();
- if (object.equals("dynamic")) {
- heightAndWidth = "height:650px;width:974px;";
- } else if (object.equals("thirds")) { // fieldGuide and mapComparison?
- heightAndWidth = "height:262px;width:262px;";
- } else {
- heightAndWidth = "height:232px;width:232px;";
- }
+ if (map != null && displayMap) {
+ String googleMapFunction = map.getGoogleMapFunction();
+
+ if (googleMapFunction != null && googleMapFunction.length() > 0) {
+ Map.addToDisplayMapCount();
+ Map.addToDisplayMapCount(mapType);
+
+ int n = googleMapFunction.indexOf("', '") + 4;
+ int o = googleMapFunction.indexOf("'", n);
+ String divName = googleMapFunction.substring(n, o);
+
+ String mapSize = "small";
+ if ("dynamic".equals(object)) {
+ mapSize = "large";
+ googleMapFunction = googleMapFunction.replaceFirst("small", "big");
+ }
+
+ if ("large".equals(mapSize) && map.getSubtitle() != null)
+ out.println("" + map.getSubtitle() + "
");
+
+ String heightAndWidth;
+ if ("dynamic".equals(object)) {
+ heightAndWidth = "height:650px;width:974px;";
+ } else if ("thirds".equals(object)) {
+ heightAndWidth = "height:262px;width:262px;";
+ } else {
+ heightAndWidth = "height:232px;width:232px;";
+ }
%>
-
-
-<%
- // We now prevent this on the server by disallowing more than 1000 points.
- //if (googleMapFunction.length() > 70000) {
- // AntwebUtil.log("info", "taxonPage-body.jsp: googleMapFunction length is " + googleMapFunction.length() + " for " + HttpUtil.getRequestInfo(request));
- //}
-
- if ((!object.equals("thirds")) && (!object.equals("dynamic"))) {
- String overviewParam = "";
-
-
- //A.log("googleMapInclude.jsp overview:" + overview);
- // This was thought to be unnecessary. It is here: http://localhost/antweb/description.do?genus=tetramorium&species=simillimum&rank=species&countryName=Brazil
- if (overview != null) {
- overviewParam = overview.getParams(); //"&" +
- }
-
- //A.log("googleMapInclude.jsp object:" + object + " objectName:" + objectName + " overviewParam:" + overviewParam);
- String objectParam = "";
- //when is this necessary?
- if ("taxonName".equals(object)) {
- objectParam = object + "=" + objectName + "&";
- }
- if ("specimen".equals(object)) {
- objectParam = object + "=" + objectName + "&";
- }
- if ("locality".equals(object)) {
- objectParam = object + "=" + objectName + "&";
- overviewParam = "";
- }
- if ("collection".equals(object)) {
- objectParam = object + "=" + objectName + "&";
- overviewParam = "";
- }
-
-%>
-
-<%
- } // We have a googleMapFunction
+
+
+
+
+<% if (!"thirds".equals(object) && !"dynamic".equals(object)) {
+ String overviewParam = "";
+ if (overview != null) overviewParam = overview.getParams();
+
+ String objectParam = "";
+ if ("taxonName".equals(object) || "specimen".equals(object)) {
+ objectParam = object + "=" + objectName + "&";
+ } else if ("locality".equals(object) || "collection".equals(object)) {
+ objectParam = object + "=" + objectName + "&";
+ overviewParam = "";
+ }
%>
-
-
+
+<% } %>
+
+
-
<%
- //A.log("googleMapInclude.jsp googleMapFunction:" + googleMapFunction);
- if ("large".equals(mapSize)) {
- if ((AntwebProps.isDevMode() || LoginMgr.isAdmin(request)) && map.getInfo() != null) out.println("Map Info: " + map.getInfo());
- }
-%>
-
-<%
- } else {
- if (AntwebProps.isDevMode()) {
- String aMessage = "No googleMapFunction";
- A.log("googleMapInclude.jsp " + aMessage);
- out.println(aMessage);
- }
+ } else {
+ if (AntwebProps.isDevMode()) {
+ String msg = "No map data found for " + object + " (" + objectName + ")";
+ out.println("" + msg + "
");
+ }
+ }
}
- } // map != null
%>
-
\ No newline at end of file
+
diff --git a/web/maps/googleMapPreInclude.jsp b/web/maps/googleMapPreInclude.jsp
index 1b3607cb..147a1409 100644
--- a/web/maps/googleMapPreInclude.jsp
+++ b/web/maps/googleMapPreInclude.jsp
@@ -1,20 +1,18 @@
-<%-- /web/maps/googleMapPreInclude.jsp (Leaflet) --%>
-<%
- // Point this to YOUR existing microservice
- final String MAPS_BASE = "http://localhost:8081";
-%>
+<%-- /web/maps/googleMapPreInclude.jsp (Local Leaflet Integration) --%>
<% if (!HttpUtil.isBot(request)) { %>
-
+
+
+
+
-
-
+
+
+
-
-
<% } %>
diff --git a/web/maps/sdk/drawMap.js b/web/maps/sdk/drawMap.js
index 5797ca5b..d695ae6e 100644
--- a/web/maps/sdk/drawMap.js
+++ b/web/maps/sdk/drawMap.js
@@ -1,25 +1,39 @@
(function (global) {
- // ---- SETTINGS ----
- const USE_CLUSTER = false; // set to true if you ever want the numeric balloons back
const DOT_STYLE = {
- radius: 5,
- color: "#ffffff", // white border stroke
- weight: 2, // thicker border
- fillColor: "#ff3b3b", // inner red
+ radius: 6,
+ color: "#ffffff",
+ weight: 2,
+ fillColor: "#ff3b3b",
fillOpacity: 1
};
+ const style = document.createElement("style");
+ style.textContent = `
+ .leaflet-container { position: relative !important; z-index: 0 !important; }
+ .leaflet-popup-content a {
+ color: #0074d9;
+ text-decoration: none;
+ font-weight: 600;
+ }
+ .leaflet-popup-content a:hover { text-decoration: underline; }
+ .leaflet-popup-content img {
+ max-width: 160px;
+ margin-top: 4px;
+ border-radius: 6px;
+ display: block;
+ }
+ `;
+ document.head.appendChild(style);
- let map = null;
- let layer = null; // LayerGroup (no cluster) OR MarkerClusterGroup
- let dots = []; // keep references to markers for fitBounds()
+ const mapRegistry = {};
- function ensureMap(divName, lat, lon, zoom) {
- if (map) return map;
+ function initMap(divName, lat, lon, zoom) {
+ if (mapRegistry[divName]) return mapRegistry[divName];
let el = document.getElementById(divName) ||
document.getElementById("map") ||
document.getElementById("map-canvas");
+
if (!el) {
el = document.createElement("div");
el.id = divName || "map";
@@ -28,91 +42,133 @@
document.body.appendChild(el);
}
- const cfg = global.__MAP_SVC_CFG__ || {};
- const tileUrl = cfg.TILE_URL || "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
- const tileAttr = cfg.TILE_ATTR || "© OpenStreetMap contributors";
-
- // preferCanvas makes thousands of circleMarkers smooth
- map = L.map(el, { preferCanvas: true })
- .setView([parseFloat(lat) || 0, parseFloat(lon) || 0], zoom || 2);
-
- L.tileLayer(tileUrl, { maxZoom: 19, attribution: tileAttr }).addTo(map);
-
- layer = USE_CLUSTER
- ? L.markerClusterGroup({ chunkedLoading: true })
- : L.layerGroup();
-
- map.addLayer(layer);
- return map;
+ const map = L.map(el, {
+ preferCanvas: true,
+ zoomControl: false,
+ attributionControl: false
+ }).setView([parseFloat(lat) || 0, parseFloat(lon) || 0], zoom || 2);
+
+ // --- Base layers ---
+ const mapLayer = L.tileLayer(
+ "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
+ { maxZoom: 19, attribution: "© OpenStreetMap contributors" }
+ );
+ const terrainLayer = L.tileLayer(
+ "https://server.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{z}/{y}/{x}",
+ { maxZoom: 13, attribution: "Terrain © Esri" }
+ );
+ const satelliteLayer = L.tileLayer(
+ "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",
+ { maxZoom: 19, attribution: "Imagery © Esri, Maxar, Earthstar Geographics" }
+ );
+ const labelOverlay = L.tileLayer(
+ "https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}",
+ { maxZoom: 19, attribution: "Labels © Esri" }
+ );
+ const hybridLayer = L.layerGroup([satelliteLayer, labelOverlay]);
+
+ mapLayer.addTo(map);
+ L.control.layers(
+ { "Map": mapLayer, "Terrain": terrainLayer, "Satellite": hybridLayer },
+ null,
+ { position: "topright" }
+ ).addTo(map);
+
+ L.control.zoom({ position: "bottomright" }).addTo(map);
+ L.control.attribution({ prefix: "", position: "bottomleft" }).addTo(map);
+ if (L.control.fullscreen) L.control.fullscreen({ position: "topright" }).addTo(map);
+
+ // --- No clustering: show all dots directly ---
+ const layer = L.layerGroup().addTo(map);
+
+ mapRegistry[divName] = { map, layer, dots: [] };
+
+ // Fix sizing for hidden grid cells
+ setTimeout(() => map.invalidateSize(), 400);
+ return mapRegistry[divName];
}
- function addDot(lat, lon, html) {
- if (lat == null || lon == null) return null;
+ function addDot(bucket, lat, lon, html) {
+ if (!bucket) return null;
const y = parseFloat(lat), x = parseFloat(lon);
- if (Number.isNaN(y) || Number.isNaN(x)) return null;
-
- // circleMarker = small red dot (no default pin, no numbers)
- const m = L.circleMarker([y, x], DOT_STYLE);
- if (html) m.bindPopup(html);
- layer.addLayer(m);
- dots.push(m);
- return m;
+ if (isNaN(y) || isNaN(x)) return null;
+
+ const marker = L.circleMarker([y, x], DOT_STYLE);
+ if (html) marker.bindPopup(html, { autoClose: true, closeButton: true });
+ marker.on("click", () => marker.openPopup());
+ bucket.layer.addLayer(marker);
+ bucket.dots.push(marker);
+ return marker;
}
- function fitToDots(padding) {
- if (!dots.length) return;
- const group = L.featureGroup(dots);
- map.fitBounds(group.getBounds(), { padding: padding || [30, 30] });
+ function fitToDots(bucket) {
+ if (!bucket || !bucket.dots.length) return;
+ const group = L.featureGroup(bucket.dots);
+ bucket.map.fitBounds(group.getBounds(), { padding: [30, 30] });
}
- // ---- Public API (keeps your current function names) ----
+ // --- Basic dot map ---
global.drawMap = function (divName, latArray, lonArray) {
- const lat0 = latArray?.length ? parseFloat(latArray[0]) : 0;
- const lon0 = lonArray?.length ? parseFloat(lonArray[0]) : 0;
- ensureMap(divName, lat0, lon0, 5);
-
- const n = Math.min(latArray?.length || 0, lonArray?.length || 0);
- for (let i = 0; i < n; i++) addDot(latArray[i], lonArray[i]);
- fitToDots();
+ const bucket = initMap(divName, latArray?.[0] || 0, lonArray?.[0] || 0, 4);
+ for (let i = 0; i < latArray.length; i++) {
+ addDot(bucket, latArray[i], lonArray[i]);
+ }
+ fitToDots(bucket);
};
+ // --- Localities ---
global.drawMapLocalities = function (divName, latArray, lonArray, nameArray, codeArray) {
- const lat0 = latArray?.length ? parseFloat(latArray[0]) : 0;
- const lon0 = lonArray?.length ? parseFloat(lonArray[0]) : 0;
- ensureMap(divName, lat0, lon0, 5);
-
- const n = Math.min(latArray?.length || 0, lonArray?.length || 0);
- for (let i = 0; i < n; i++) {
- const name = (nameArray && nameArray[i]) || "";
- const code = (codeArray && codeArray[i]) || "";
- const html = `Locality: ${name}
Code: ${code}
`;
- addDot(latArray[i], lonArray[i], html);
+ const bucket = initMap(divName, latArray?.[0] || 0, lonArray?.[0] || 0, 5);
+ for (let i = 0; i < latArray.length; i++) {
+ const name = nameArray?.[i] || "";
+ const code = codeArray?.[i] || "";
+ const url = `/locality.do?code=${encodeURIComponent(code)}`;
+ const html = `
+ `;
+ addDot(bucket, latArray[i], lonArray[i], html);
}
- fitToDots();
+ fitToDots(bucket);
};
+ // --- Specimens ---
global.drawMapSpecimens = function (divName, latArray, lonArray, nameArray, codeArray, imageArray) {
- const lat0 = latArray?.length ? parseFloat(latArray[0]) : 0;
- const lon0 = lonArray?.length ? parseFloat(lonArray[0]) : 0;
- ensureMap(divName, lat0, lon0, 5);
-
- const n = Math.min(latArray?.length || 0, lonArray?.length || 0);
- for (let i = 0; i < n; i++) {
- const name = (nameArray && nameArray[i]) || "";
- const code = (codeArray && codeArray[i]) || "";
- const img = (imageArray && imageArray[i]) || "";
- const imgTag = img ? `
` : "";
- const html = `Specimen: ${name}
Code: ${code}${imgTag}
`;
- addDot(latArray[i], lonArray[i], html);
+ const bucket = initMap(divName, latArray?.[0] || 0, lonArray?.[0] || 0, 5);
+ for (let i = 0; i < latArray.length; i++) {
+ const name = nameArray?.[i] || "";
+ const code = codeArray?.[i] || "";
+ const img = imageArray?.[i] || "";
+ const url = `/specimen.do?name=${encodeURIComponent(code)}`;
+ const imgTag = img ? `
` : "";
+ const html = `
+ `;
+ addDot(bucket, latArray[i], lonArray[i], html);
}
- fitToDots();
+ fitToDots(bucket);
};
+ // --- Single Point ---
global.drawMapSinglePoint = function (divName, lat, lon, name, code, img) {
- ensureMap(divName, parseFloat(lat) || 0, parseFloat(lon) || 0, 8);
- const imgTag = img ? `
` : "";
- const html = `${name || ""}
${code || ""}${imgTag}
`;
- addDot(lat, lon, html);
- fitToDots();
+ const bucket = initMap(divName, lat, lon, 8);
+ const url = `/specimen.do?name=${encodeURIComponent(code)}`;
+ const imgTag = img ? `
` : "";
+ const html = `
+ `;
+ addDot(bucket, lat, lon, html);
};
+
+ // --- Resize all maps on load ---
+ document.addEventListener("DOMContentLoaded", () => {
+ setTimeout(() => {
+ Object.values(mapRegistry).forEach(b => b.map.invalidateSize());
+ }, 800);
+ });
})(window);