Skip to content

Commit 74e8342

Browse files
WIP hillshade
1 parent e874fc0 commit 74e8342

File tree

7 files changed

+186
-153
lines changed

7 files changed

+186
-153
lines changed

src/TopoMap.svelte

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import cachingSource from './sources/cachingSource'
2222
import fragment from './stores/fragment'
2323
import { nzBounds } from './utils/bounds'
24+
import { XYZ } from 'ol/source'
2425
</script>
2526

2627
<div id="topo-map">
@@ -31,17 +32,25 @@
3132
maxZoom={18}
3233
minZoom={4}
3334
extent={nzBounds}
34-
initialView={nzBounds}
35-
/>
35+
initialView={nzBounds} />
3636
<MapPositioner />
3737
<LayerGroup title="Base Layers">
38-
{#each layerDefinitions as definition}
38+
{#each layerDefinitions.filter((l) => l.type === 'base') as definition}
3939
<TileLayer
4040
title={definition.name}
4141
type="base"
4242
visible={false}
43-
source={cachingSource(definition)}
44-
/>
43+
source={cachingSource(definition)} />
44+
{/each}
45+
</LayerGroup>
46+
<LayerGroup title="Overlays">
47+
{#each layerDefinitions.filter((l) => l.type === 'overlay') as definition}
48+
<TileLayer
49+
title={definition.name}
50+
prerender={definition.prerender}
51+
type={"overlay"}
52+
visible={definition.defaultVisible}
53+
source={cachingSource(definition)} />
4554
{/each}
4655
</LayerGroup>
4756
<LayerGroup title="Features">
@@ -54,8 +63,7 @@
5463
on:change={(e) => {
5564
const [lng, lat] = [e.detail.result.lon, e.detail.result.lat]
5665
$fragment.label = { lat, lng, text: e.detail.result.name }
57-
}}
58-
/>
66+
}} />
5967
<MapZoom />
6068
<MapLocator />
6169
<MapMeasure />

src/components/MapFeatures.svelte

Lines changed: 109 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,121 @@
11
<script lang="ts">
2-
import BaseLayer from "ol/layer/Base";
3-
import LayerGroup from "ol/layer/Group";
4-
import { getOlContext } from "../ol/Map.svelte";
5-
import MapControl from "./MapControl.svelte";
6-
import grow from "../transitions/grow";
7-
import fragment from "../stores/fragment";
2+
import BaseLayer from 'ol/layer/Base'
3+
import LayerGroup from 'ol/layer/Group'
4+
import { getOlContext } from '../ol/Map.svelte'
5+
import MapControl from './MapControl.svelte'
6+
import grow from '../transitions/grow'
7+
import fragment from '../stores/fragment'
88
9-
const { map } = getOlContext();
10-
let open = false;
9+
const { map } = getOlContext()
10+
let open = false
1111
12-
const getAllLayers = (): BaseLayer[] =>
13-
map
14-
.getLayers()
15-
.getArray()
16-
.reduce(
17-
(prev, next) => [
18-
...prev,
19-
...[next, ...next.getLayersArray()].filter((l) => l),
20-
],
21-
[]
22-
)
23-
.filter((l) => l.get("title"));
24-
let allLayers = getAllLayers();
25-
// Handle layers changing.
26-
map.getLayers().on("change", () => (allLayers = getAllLayers()));
27-
$: {
28-
for (const layer of allLayers) {
29-
layer.once("change", () => (allLayers = getAllLayers()));
30-
}
12+
const getAllLayers = (): BaseLayer[] =>
13+
map
14+
.getLayers()
15+
.getArray()
16+
.reduce(
17+
(prev, next) => [
18+
...prev,
19+
...[next, ...next.getLayersArray()].filter((l) => l),
20+
],
21+
[],
22+
)
23+
.filter((l) => l.get('title'))
24+
let allLayers = getAllLayers()
25+
// Handle layers changing.
26+
map.getLayers().on('change', () => (allLayers = getAllLayers()))
27+
$: {
28+
for (const layer of allLayers) {
29+
layer.once('change', () => (allLayers = getAllLayers()))
3130
}
31+
}
3232
33-
$: nonGroupLayers = allLayers.filter((l) => !(l instanceof LayerGroup));
33+
$: nonGroupLayers = allLayers.filter((l) => !(l instanceof LayerGroup))
3434
35-
$: baseLayers = nonGroupLayers.filter((l) => l.get("type") === "base");
36-
$: {
37-
// Handle selecting a different base layer.
38-
for (const baseLayer of baseLayers) baseLayer.set("visible", false);
39-
baseLayers[$fragment.baseLayer].set("visible", true);
40-
}
35+
$: baseLayers = nonGroupLayers.filter((l) => l.get('type') === 'base')
36+
$: {
37+
// Handle selecting a different base layer.
38+
for (const baseLayer of baseLayers) baseLayer.set('visible', false)
39+
baseLayers[$fragment.baseLayer].set('visible', true)
40+
}
4141
42-
$: featureLayers = nonGroupLayers.filter((l) => l.get("type") !== "base");
43-
$: canSelectAllFeatures = $fragment.featureLayers.length !== featureLayers.length;
44-
$: canSelectNoFeatures = $fragment.featureLayers.length !== 0;
45-
const setLayerVisible = (layer, visible: boolean) => {
46-
fragment.update(u => {
47-
const id = layer.get('id')
48-
return {
49-
...u,
50-
featureLayers: visible ? [...u.featureLayers, id].filter(l => l) : u.featureLayers.filter(u => u !== id)
51-
}
52-
})
53-
}
54-
const toggleAllFeatures = (visible: boolean) => {
55-
if (!visible) $fragment.featureLayers = []
56-
else $fragment.featureLayers = featureLayers.map(l => l.get('id'))
57-
};
42+
$: featureLayers = nonGroupLayers.filter((l) => l.get('type') !== 'base')
43+
$: canSelectAllFeatures =
44+
$fragment.featureLayers.length !== featureLayers.length
45+
$: canSelectNoFeatures = $fragment.featureLayers.length !== 0
46+
const setLayerVisible = (layer, visible: boolean) => {
47+
fragment.update((u) => {
48+
const id = layer.get('id')
49+
layer.setVisible(visible)
50+
return {
51+
...u,
52+
featureLayers: visible
53+
? [...u.featureLayers, id].filter((l) => l)
54+
: u.featureLayers.filter((u) => u !== id),
55+
}
56+
})
57+
}
58+
const toggleAllFeatures = (visible: boolean) => {
59+
if (!visible) $fragment.featureLayers = []
60+
else $fragment.featureLayers = featureLayers.map((l) => l.get('id'))
61+
}
5862
</script>
5963

6064
<MapControl position="topright">
61-
<div
62-
class="shadow rounded"
63-
on:mouseenter={(e) => (open = true)}
64-
on:mouseleave={(e) => (open = false)}>
65-
<button class="map-button"> <span class="-ml-1">↔️</span> </button>
66-
{#if open}
67-
<div
68-
transition:grow
69-
class="flex overflow-hidden w-52 bg-background py-2 px-4 rounded-b">
70-
<div class="flex-shrink-0">
71-
<h4 class="text-foreground font-bold">Base Maps</h4>
72-
{#each baseLayers as layer, index}
73-
<div>
74-
<label>
75-
<input
76-
type="radio"
77-
bind:group={$fragment.baseLayer}
78-
value={index} />
79-
{layer.get('title')}
80-
</label>
81-
</div>
82-
{/each}
83-
<div class="w-auto border-t border-foreground my-2" />
84-
<h4 class="text-foreground font-bold">
85-
Features
86-
{#if canSelectAllFeatures}
87-
<button
88-
class="appearance-none text-blue-500 active:text-blue-400 hover:underline focus:outline-none"
89-
on:click={() => toggleAllFeatures(true)}>
90-
All
91-
</button>
92-
{/if}
93-
{#if canSelectNoFeatures}
94-
<button
95-
class="appearance-none text-blue-500 active:text-blue-400 hover:underline focus:outline-none"
96-
on:click={() => toggleAllFeatures(false)}>
97-
None
98-
</button>
99-
{/if}
100-
</h4>
101-
{#each featureLayers as layer}
102-
<div>
103-
<label>
104-
<input
105-
type="checkbox"
106-
checked={$fragment.featureLayers.includes(layer.get('id'))}
107-
on:change={(e) => setLayerVisible(layer, e.target['checked'])} />
108-
{layer.get('title')}
109-
</label>
110-
</div>
111-
{/each}
112-
</div>
65+
<div
66+
class="shadow rounded"
67+
on:mouseenter={(e) => (open = true)}
68+
on:mouseleave={(e) => (open = false)}>
69+
<button class="map-button"> <span class="-ml-1">↔️</span> </button>
70+
{#if open}
71+
<div
72+
transition:grow={{ duration: 100 }}
73+
class="flex overflow-hidden w-52 bg-background py-2 px-4 rounded-b">
74+
<div class="flex-shrink-0">
75+
<h4 class="text-foreground font-bold">Base Maps</h4>
76+
{#each baseLayers as layer, index}
77+
<div>
78+
<label>
79+
<input
80+
type="radio"
81+
bind:group={$fragment.baseLayer}
82+
value={index} />
83+
{layer.get('title')}
84+
</label>
85+
</div>
86+
{/each}
87+
<div class="w-auto border-t border-foreground my-2" />
88+
<h4 class="text-foreground font-bold">
89+
Features
90+
{#if canSelectAllFeatures}
91+
<button
92+
class="appearance-none text-blue-500 active:text-blue-400 hover:underline focus:outline-none"
93+
on:click={() => toggleAllFeatures(true)}>
94+
All
95+
</button>
96+
{/if}
97+
{#if canSelectNoFeatures}
98+
<button
99+
class="appearance-none text-blue-500 active:text-blue-400 hover:underline focus:outline-none"
100+
on:click={() => toggleAllFeatures(false)}>
101+
None
102+
</button>
103+
{/if}
104+
</h4>
105+
{#each featureLayers as layer}
106+
<div>
107+
<label>
108+
<input
109+
type="checkbox"
110+
checked={$fragment.featureLayers.includes(layer.get('id'))}
111+
on:change={(e) =>
112+
setLayerVisible(layer, e.target['checked'])} />
113+
{layer.get('title')}
114+
</label>
113115
</div>
114-
{/if}
115-
</div>
116+
{/each}
117+
</div>
118+
</div>
119+
{/if}
120+
</div>
116121
</MapControl>

src/components/SettingsView.svelte

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
2323
onMount(() => {
2424
for (const layer of layerDefinitions) {
25-
if (!$settings.baseLayers[layer.name]) {
26-
$settings.baseLayers[layer.name] = {
25+
if (!$settings.layers[layer.name]) {
26+
$settings.layers[layer.name] = {
2727
cache: false,
2828
}
2929
}
@@ -42,9 +42,9 @@
4242
<div class="text-gray-500 italic">{layer.description}</div>
4343
<Checkbox
4444
label="Cache Viewed Tiles"
45-
checked={$settings.baseLayers[layer.name].cache}
45+
checked={$settings.layers[layer.name].cache}
4646
on:change={(e) =>
47-
settings.updateBaseLayer(layer.name, {
47+
settings.updateLayer(layer.name, {
4848
cache: e.target['checked'],
4949
})} />
5050
<div>

src/layers/layerDefinitions.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
import BaseLayer from "ol/layer/Base";
22
import { TileCoord } from "ol/tilecoord";
33
import { pickRandom } from "../utils/random";
4+
import RenderEvent from "ol/render/Event";
45

56
export interface BaseLayerDefinition {
7+
id: string;
68
name: string;
79
description: string;
10+
defaultVisible?: boolean;
811
}
912

1013
export type TileLayerDefinition = BaseLayerDefinition & {
11-
type: 'base';
14+
type: 'base' | 'overlay';
1215
url: string | ((tile: TileCoord) => string);
16+
prerender?: (e: RenderEvent) => void
1317
}
1418

1519
export interface FeatureLayerDefinition {
@@ -21,6 +25,7 @@ export type LayerDefinition = BaseLayerDefinition
2125

2226
export const linzTopo: TileLayerDefinition =
2327
{
28+
id: 'linz-topo',
2429
name: "LINZ Topo",
2530
description: "The LINZ topographic map",
2631
type: "base",
@@ -33,23 +38,35 @@ export const linzTopo: TileLayerDefinition =
3338
export const layerDefinitions: TileLayerDefinition[] = [
3439
linzTopo,
3540
{
41+
id: 'osm',
3642
name: "Open Street Maps",
3743
description: "Street maps provided by openstreetmaps",
3844
type: 'base',
3945
url: 'https://{a-c}.tile.openstreetmap.org/{z}/{x}/{y}.png'
4046
},
4147
{
48+
id: 'open-topo-maps',
4249
name: "Open Topo Maps",
4350
description: "Topographic maps based on publicly available data",
4451
type: 'base',
4552
url: 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png'
4653
},
4754
{
55+
id: 'linz-arial',
4856
name: 'LINZ Aerial Imagery',
4957
description: "High resolution imagery of New Zealand, provided by LINZ",
5058
type: 'base',
5159
url: ([z,x,y]: TileCoord) => {
5260
return `https://basemaps.linz.govt.nz/v1/tiles/aerial/EPSG:3857/${z}/${x}/${y}.webp?api=d01fbtg0ar3v159zx4e0ajt0n09`;
5361
}
62+
},
63+
{
64+
id: 'hillshade',
65+
name: "Hillshade",
66+
description: "Terrain relief overlay",
67+
type: 'overlay',
68+
defaultVisible: true,
69+
prerender: e => e.context.canvas.getContext('2d').globalCompositeOperation = 'multiply',
70+
url: 'https://tiles-cdn.koordinates.com/services;key=d0772bed2204423f87157f7fb1223389/tiles/v4/layer=50765/EPSG:3857/{z}/{x}/{y}.png'
5471
}
5572
]

0 commit comments

Comments
 (0)