diff --git a/src/App.css b/src/App.css index ace060a..77ae94d 100644 --- a/src/App.css +++ b/src/App.css @@ -66,7 +66,35 @@ h1 { align-self: center; } +.draw { + display: flex; + justify-content: space-around; + margin-bottom: 20px; +} + +button.active { + background-color: darkslategray; + color: white; +} + +@media (prefers-color-scheme: dark) { + button.active { + background-color: yellow; + color: black; + } + .maplibregl-popup-content { + background: #242424; + } + + .maplibregl-popup-anchor-bottom .maplibregl-popup-tip { + border-top-color: #242424; + } + + .maplibregl-popup-anchor-top .maplibregl-popup-tip { + border-bottom-color: #242424; + } +} @media screen and (max-width: 768px) { .container { diff --git a/src/App.tsx b/src/App.tsx index 429a580..57261e1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import { createSignal, createEffect, onMount } from "solid-js"; +import "maplibre-gl/dist/maplibre-gl.css"; import "./App.css"; import maplibregl from "maplibre-gl"; -import "maplibre-gl/dist/maplibre-gl.css"; import { r1, s2, s1 } from "s2js"; import { greatCircle } from "@turf/great-circle"; import { flatten } from "@turf/flatten"; @@ -10,9 +10,8 @@ import { TerraDraw, TerraDrawMapLibreGLAdapter, TerraDrawRectangleMode, - TerraDrawAngledRectangleMode, TerraDrawPolygonMode, - TerraDrawCircleMode, + TerraDrawRenderMode, } from "terra-draw"; import { Feature, @@ -227,8 +226,11 @@ const getCellVisualization = (union: s2.CellUnion): FeatureCollection => { .map((f) => f.geometry.coordinates) .flat(1); + const center = s2.LatLng.fromPoint(cell.center()); + return { type: "Feature", + id: cell.id.toString(), geometry: { type: "Polygon", coordinates: [coordinates], @@ -236,6 +238,8 @@ const getCellVisualization = (union: s2.CellUnion): FeatureCollection => { properties: { level: cell.level, token: s2.cellid.toToken(cell.id), + centerLng: s1.angle.degrees(center.lng), + centerLat: s1.angle.degrees(center.lat), }, }; }); @@ -252,6 +256,7 @@ function App() { const [maxLevel, setMaxLevel] = createSignal(30); const [maxCells, setMaxCells] = createSignal(200); + const [drawMode, setDrawMode] = createSignal(""); const updateCovering = () => { const snapshot = draw!.getSnapshot(); @@ -264,12 +269,10 @@ function App() { let covering; switch (snapshot[0].properties.mode) { case "rectangle": { - console.info("rectangle covering"); covering = getCovering(regionCoverer, polygons, rectBuilder); break; } default: { - console.info("polygon covering"); covering = getCovering(regionCoverer, polygons, polygonBuilder); } } @@ -293,6 +296,13 @@ function App() { updateCovering(); }; + const startDrawMode = (mode: string) => { + if (draw) { + draw.setMode(mode); + setDrawMode(mode); + } + }; + onMount(() => { let basemapTheme = "white"; let cellColor = "darkslategray"; @@ -320,18 +330,19 @@ function App() { adapter: new TerraDrawMapLibreGLAdapter({ map, maplibregl }), modes: [ new TerraDrawRectangleMode(options), - new TerraDrawAngledRectangleMode(options), + new TerraDrawRenderMode({ modeName: "render", styles: {} }), new TerraDrawPolygonMode(options), - new TerraDrawCircleMode(options), ], }); draw.on("finish", () => { + startDrawMode("render"); updateCovering(); }); draw.start(); - draw.setMode("rectangle"); + + startDrawMode("rectangle"); map.on("load", () => { map.addSource("covering", { @@ -347,7 +358,12 @@ function App() { source: "covering", paint: { "fill-color": cellColor, - "fill-opacity": 0.5, + "fill-opacity": [ + "case", + ["boolean", ["feature-state", "hover"], false], + 0.7, + 0.5, + ], }, }); map.addLayer({ @@ -373,6 +389,52 @@ function App() { }, }); + let hoveredCellId:string | number | undefined; + + map.on("mousemove", "covering-fill", (e) => { + if (e.features && e.features.length > 0) { + if (hoveredCellId) { + map.setFeatureState( + { source: "covering", id: hoveredCellId }, + { hover: false }, + ); + } + hoveredCellId = e.features[0].id; + map.setFeatureState( + { source: "covering", id: hoveredCellId }, + { hover: true }, + ); + } + map.getCanvas().style.cursor = "pointer"; + }); + + map.on("mouseleave", "covering-fill", () => { + map.getCanvas().style.cursor = ""; + }); + + map.on("click", "covering-fill", (e) => { + if (!e || !e.features || e.features.length === 0) return; + const properties = e.features[0].properties; + const coordinates = { + lng: properties.centerLng, + lat: properties.centerLat, + }; + + while (Math.abs(e.lngLat.lng - coordinates.lng) > 180) { + coordinates.lng += e.lngLat.lng > coordinates.lng ? 360 : -360; + } + + new maplibregl.Popup({ closeButton: false }) + .setLngLat(coordinates) + .setHTML( + `
+
Level ${properties.level}
+
${properties.token}
+
`, + ) + .addTo(map); + }); + // initialize the view with a predefined union const init = initialUnion(); if (init && init.length) { @@ -387,24 +449,21 @@ function App() {