Skip to content

Commit

Permalink
feat: draw polygon
Browse files Browse the repository at this point in the history
  • Loading branch information
coderbyheart committed Dec 29, 2024
1 parent 45b2c88 commit f06d695
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 8 deletions.
7 changes: 7 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
},
"dependencies": {
"@sinclair/typebox": "0.34.13",
"lucide": "0.469.0",
"maplibre-gl": "4.7.1",
"polylabel": "2.0.1",
"solid-js": "1.9.3"
Expand Down
14 changes: 13 additions & 1 deletion src/FullScreenMap.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
#map {
#map-container {
width: 100%;
height: calc(100% - 64px);
position: absolute;
top: 64px;
left: 0;
}

#map {
width: 100%;
height: 100%;
}

#map-container > nav {
position: absolute;
right: 1rem;
top: 1rem;
z-index: 1000;
}
119 changes: 115 additions & 4 deletions src/FullScreenMap.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Map as MapLibreGlMap, Marker, Popup } from 'maplibre-gl'
import { LngLat, Map as MapLibreGlMap, Marker, Popup } from 'maplibre-gl'
import polylabel from 'polylabel'
import { createEffect, onCleanup } from 'solid-js'
import { createEffect, createSignal, onCleanup, Show } from 'solid-js'
import { useData } from './context/Data.js'
import { Draw, NoDraw, Remove } from './LucideIcon.jsx'
import { createMap } from './map/createMap.js'
import { polylabelToCoordinates } from './polylabelToCoordinates.jsx'

import './FullScreenMap.css'
import { polylabelToCoordinates } from './polylabelToCoordinates.jsx'

const asNumber = (s: string, fallback: number) => {
const n = Number(s)
Expand All @@ -17,6 +18,9 @@ const asNumber = (s: string, fallback: number) => {

const FullScreenMap = () => {
const data = useData()
const [drawing, setDrawing] = createSignal(false)
const [coords, setCoords] = createSignal<Array<LngLat>>([])
const [loaded, setLoaded] = createSignal(false)

let ref!: HTMLDivElement
let map: MapLibreGlMap
Expand All @@ -36,6 +40,7 @@ const FullScreenMap = () => {
)

map.on('load', () => {
setLoaded(true)
for (const sink of data.carbonSinks) {
const polyCenter =
sink.polygon !== undefined
Expand Down Expand Up @@ -119,13 +124,119 @@ const FullScreenMap = () => {
`${import.meta.env.BASE_URL}#map/${lat.toFixed(6)}/${lng.toFixed(6)}/${map.getZoom()}`,
)
})

map.on('click', (e) => {
if (drawing()) {
setCoords([...coords(), e.lngLat])
}
})
})

// Draw first point
createEffect(() => {
if (map === undefined) return
if (loaded() === false) return
if (map.getSource('start-point') !== undefined) {
map.removeLayer('start-point-drawing')
map.removeSource('start-point')
}
if (coords().length === 1) {
const point = coords()[0]
map.addSource('start-point', {
type: 'geojson',
data: {
type: 'Feature',
properties: {},
geometry: {
type: 'Point',
coordinates: [point.lng, point.lat],
},
},
})
map.addLayer({
id: 'start-point-drawing',
type: 'circle',
source: 'start-point',
paint: {
'circle-color': '#80ed99',
'circle-radius': 10,
'circle-stroke-width': 2,
'circle-stroke-color': '#222222',
},
})
}
})

// Draw polygon
createEffect(() => {
if (map === undefined) return
if (loaded() === false) return
if (coords().length < 1) return
if (map.getSource('drawing') !== undefined) {
map.removeLayer('drawing')
map.removeLayer('drawing-dots')
map.removeSource('drawing')
}
map.addSource('drawing', {
type: 'geojson',
data: {
type: 'Feature',
properties: {},
geometry: {
type: 'Polygon',
coordinates: [coords().map((c) => [c.lng, c.lat])],
},
},
})
map.addLayer({
id: 'drawing',
type: 'fill',
source: 'drawing',
layout: {},
paint: {
'fill-color': '#088',
'fill-opacity': 0.8,
},
})
map.addLayer({
id: 'drawing-dots',
type: 'circle',
source: 'drawing',
paint: {
'circle-color': '#80ed99',
'circle-radius': 10,
'circle-stroke-width': 2,
'circle-stroke-color': '#222222',
},
})
})

onCleanup(() => {
map?.remove()
})

return <div id="map" ref={ref} />
return (
<div id="map-container">
<nav>
<Show
when={drawing()}
fallback={
<button onClick={() => setDrawing(true)}>
<Draw />
</button>
}
>
<button onClick={() => setDrawing(false)}>
<NoDraw />
</button>
<button onClick={() => setCoords(coords().slice(0, -1))}>
<Remove />
</button>
</Show>
</nav>
<div id="map" ref={ref} />
</div>
)
}

export default FullScreenMap
Expand Down
46 changes: 46 additions & 0 deletions src/LucideIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Delete, IconNode, Pencil, PencilOff } from 'lucide'
import { For } from 'solid-js'
import { Dynamic } from 'solid-js/web'

export const LucideIcon = (
props: {
icon: IconNode
} & LucideProps,
) => {
const [, attrs, children] = props.icon
const svgProps = {
'stroke-width': props.strokeWidth ?? attrs.strokeWidth ?? 2,
}
return (
<svg
{...{ ...attrs, ...svgProps }}
style={{
width: `${props.size ?? 24}px`,
height: `${props.size ?? 24}px`,
}}
class={`icon ${props.class ?? ''}`}
>
<For each={children}>
{([elementName, attrs]) => (
<Dynamic component={elementName} {...attrs} />
)}
</For>
</svg>
)
}

export type LucideProps = {
size?: number
strokeWidth?: number
class?: string
}

export const Draw = (props: LucideProps) => (
<LucideIcon icon={Pencil} {...props} />
)
export const NoDraw = (props: LucideProps) => (
<LucideIcon icon={PencilOff} {...props} />
)
export const Remove = (props: LucideProps) => (
<LucideIcon icon={Delete} {...props} />
)
4 changes: 2 additions & 2 deletions src/Navigation.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
nav {
nav.main {
display: flex;
justify-content: space-between;
align-items: center;
Expand All @@ -10,7 +10,7 @@ nav {
height: 64px;
}

nav a {
nav.main a {
text-decoration: none;
color: inherit;
}
2 changes: 1 addition & 1 deletion src/Navigation.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import './Navigation.css'

const Navigation = () => (
<nav>
<nav class="main">
<a href={`${import.meta.env.BASE_URL}#`}>
<h1>Forest Sync</h1>
</a>
Expand Down

0 comments on commit f06d695

Please sign in to comment.