<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Lawrence Deer Club Maps</title> <link rel="stylesheet" href="/leaflet/leaflet.css" /> <script src="/leaflet/leaflet.js"></script> <style> html, body { margin: 0; padding: 0; } #map { height: 100vh; width: 100vw; } div.popup dt { font-weight: bold; } .label { font-weight: bold; text-align: center; margin-top: -1em; } .label div { position: relative; left: -50%; top: -50%; text-shadow: 0 0 2px white; } .leaflet-popup-content-wrapper { height: min(800px, 60vh); overflow-y: auto; } #popupcontainer { position: relative; z-index: 1000; } #popupcontainer .leaflet-popup-content-wrapper { height: unset; } #popupcontainer .leaflet-popup { transform: none !important; position: fixed; width: 100vw; height: 100vh; right: 0; left: 0 !important; top: 1em; bottom: 1em !important; overflow: auto; } #popupcontainer .leaflet-popup-content { width: initial; } /* BEGIN lightbox */ /* simplified from https://jekyllcodex.org/without-plugin/lightbox/ */ #lightbox { width: 100%; height: 100%; position: fixed; top: 0; left: 0; background: rgba(0,0,0,0.85); z-index: 9999999; line-height: 0; cursor: pointer; display: none; } #lightbox .img { position: relative; top: 50%; left: 50%; transform: translate(-50%,-50%); max-width: 100%; max-height: 100%; } #lightbox .img img { opacity: 0; pointer-events: none; width: auto; } @media screen and (min-width: 1200px) { #lightbox .img { max-width: 1200px; } } @media screen and (min-height: 1200px) { #lightbox .img { max-height: 1200px; } } #lightbox #close { height: 50px; width: 50px; position: fixed; cursor: pointer; text-decoration: none; z-index: 99; right: 0; top: 0; } #lightbox #close:after, #lightbox #close:before { position: absolute; margin-top: 22px; margin-left: 14px; content: ""; height: 3px; background: white; width: 23px; transform-origin: 50% 50%; transform: rotate(-45deg); } #lightbox #close:after { transform: rotate(45deg); } #lightbox, #lightbox * { user-select: none; } /* END lightbox */ </style> </head> <body> <div id="map"></div> <div id="popupcontainer" class="leaflet-container"></div> <div id="lightbox" onclick="this.style.display = 'none';"></div> <script> function displayLightboxOnClick(target, event) { event.preventDefault(); document.getElementById('lightbox').innerHTML = ` <a id="close"></a> <div class="img" style="background: url('${target.getAttribute('href')}') center center / contain no-repeat;"> <img src="${target.getAttribute('href')}"> </div>`; document.getElementById('lightbox').style.display = 'block'; } function calculateCentroid(points) { // https://en.wikipedia.org/wiki/Centroid#Of_a_polygon let area = 0; let centroidLat = 0; let centroidLng = 0; for (let i = 0; i < points.length; i++) { const { lat: lat1, lng: lng1 } = points[i]; const { lat: lat2, lng: lng2 } = points[(i + 1) % points.length]; // Next vertex, wrapping around const determinant = lat1 * lng2 - lng1 * lat2; area += determinant; centroidLat += (lat1 + lat2) * determinant; centroidLng += (lng1 + lng2) * determinant; } area *= 0.5; centroidLat /= (6 * area); centroidLng /= (6 * area); return [centroidLat, centroidLng]; } (async function() { const map = L.map('map', { minZoom: 15, maxZoom: 21, maxBounds: [ [47.517085, -93.427584], [47.457925, -93.340026], ], }); // L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { // maxZoom: 19, // attribution: '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>' // }).addTo(map); L.tileLayer('./satellite/{z}/{x}/{y}.jpg', { minNativeZoom: 12, maxNativeZoom: 20, maxZoom: 21, bounds: [ [47.517085, -93.427584], [47.457925, -93.340026], ], }).addTo(map); // Make these requests in parallel const track_req_promise = fetch('data/track.geojson'); const plats_req_promise = fetch('data/plats.geojson'); const data_req_promise = fetch('data/data.json'); const track_req = await track_req_promise; const plats_req = await plats_req_promise; const data_req = await data_req_promise; const track = await track_req.json(); const plats = await plats_req.json(); const data = await data_req.json(); L.geoJSON(plats, { filter: feature => !feature.properties.TAO_NAME.toLowerCase().includes("lawrence deer club"), onEachFeature: function(feature, layer) { const centroid = calculateCentroid(layer.getLatLngs()[0]); const label = L.marker(centroid, { icon: L.divIcon({ iconSize: null, className: "label", html: "<div>" + feature.properties.TAO_NAME + "</div>" }) }).addTo(map); }, }).addTo(map); L.geoJSON(track, { style: { color: "maroon", }, }).addTo(map); for (let stand of data.stands) { if (stand.location) { const marker = L.marker(stand.location).addTo(map); const popupContentWrapper = document.createElement('div'); popupContentWrapper.classList.add('popup'); let attributesString = '<dl>'; for (let [attribute, value] of Object.entries(stand.attributes)) { attributesString += `<dt>${attribute}</dt><dd>${value}</dd>`; } attributesString += '</dl>'; popupContentWrapper.innerHTML = ` <h2>${stand.name}</h2> <a href="images/${stand.image}" onclick="displayLightboxOnClick(this, event)"><img style="width: min(80vw, 300px);" src="images/thumbs/${stand.image}"></a> ${attributesString} `; let popupOptions = {}; // TODO: evaluate this when creating popup instead of at // page instantiation time -- if the page is resized, this // won't keep up with those changes. if (window.visualViewport.width < 600) { popupOptions.pane = document.getElementById('popupcontainer'); } marker.bindPopup(popupContentWrapper, popupOptions); } } map.fitBounds([ [47.4865,-93.4068], [47.4992,-93.3746], ]); })(); </script> </body> </html>