Skip to content

Instantly share code, notes, and snippets.

@mpiannucci
Last active December 15, 2022 17:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mpiannucci/b1c6a5dfd6cd9954848359b6a06a616f to your computer and use it in GitHub Desktop.
Save mpiannucci/b1c6a5dfd6cd9954848359b6a06a616f to your computer and use it in GitHub Desktop.
Client Side Contouring WMS Data with d3
.fill-window {
height: 100%;
position: absolute;
left: 0;
width: 100%;
overflow: hidden;
}
body {
margin: 0px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>GFS Wave SWH - Vectorized On Demand</title>
<link rel="stylesheet" href="./index.css">
<script src="https://cdn.jsdelivr.net/npm/d3-array@3"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-contour@4"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-color@3"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-interpolate@3"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-scale@4"></script>
<script src="https://cdn.jsdelivr.net/npm/d3-scale-chromatic@3"></script>
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.0.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.0.0/mapbox-gl.css' rel='stylesheet' />
</head>
<body class="fill-window"></body>
<div id="map" class="fill-window"></div>
<script src="./index.js"></script>
<!-- <div class="controls">
</div> -->
</body>
</html>
const map = new mapboxgl.Map({
container: document.getElementById('map'),
style: 'mapbox://styles/mapbox/dark-v8',
center: [-74.0, 39.0],
zoom: 2,
});
map.on('load', async () => {
const width = 512;
const height = 512;
const imageSize = width * height;
const colorMin = 0;
const colorMax = 15;
const colorRange = colorMax - colorMin;
const colorSteps = 250
const colorScale = d3.scaleSequential(d3.interpolateRdBu).domain([6, 0]);
const minLng = -180.0;
const maxLng = 180.0;
const minLat = -90;
const maxLat = 90;
const response = await fetch(`https://eds.ioos.us/ncWMS2/wms?REQUEST=GetMap&VERSION=1.3.0&STYLES=raster/seq-Greys-inv&colorscalerange=${colorMin},${colorMax}&CRS=CRS:84&WIDTH=${width}&HEIGHT=${height}&FORMAT=image/png&TRANSPARENT=true&LAYERS=GFS_WAVE_GLOBAL/Significant_height_of_combined_wind_waves_and_swell_surface&BBOX=${minLng},${minLat},${maxLng},${maxLat}&time=2022-12-15T16:00:00.000Z`);
const blob = await response.blob();
const bmp = await createImageBitmap(blob);
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const context = canvas.getContext('2d');
context.drawImage(bmp, 0, 0);
const data = context.getImageData(0, 0, width, height).data;
const values = new Float64Array(imageSize);
for (let i = 0; i < imageSize; ++i) {
values[i] = colorMin + ((data[i * 4] / 255) * (colorRange));
}
const maskedValues = new Float64Array(imageSize);
for (let i = 0; i < imageSize; ++i) {
const alpha = data[(i * 4) + 3];
maskedValues[i] = alpha === 255 ? values[i] : -999;
}
const polygonContours = d3.contours()
.size([width, height])
.smooth(true)
.thresholds(Array.from({ length: colorSteps }, (_, i) => colorMin + (i / colorSteps) * colorRange))
(maskedValues);
const polygonFeatureCollection = {
type: 'FeatureCollection',
features: polygonContours.map(p => ({
type: 'Feature',
properties: {
value: p.value,
color: colorScale(p.value),
},
geometry: {
type: 'MultiPolygon',
coordinates: p.coordinates
.map(ring =>
ring.map(coords =>
coords
.map(point => ([
minLng + (maxLng - minLng) * (point[0] / width),
maxLat - (maxLat - minLat) * (point[1] / height)
]))
.map(point => ([
point[0],
point[1] > 90 ? point[1] - 90 : point[1],
]))
)
)
}
})),
};
const blurredValues = d3.blur2({ data: [...values], width }, 0.5).data;
for (let i = 0; i < imageSize; ++i) {
const alpha = data[(i * 4) + 3];
blurredValues[i] = alpha === 255 ? blurredValues[i] : -999;
}
const lineContours = d3.contours()
.size([width, height])
.thresholds([1, 2, 3, 4, 5])
.smooth(true)
(blurredValues);
const lineFeatureCollection = {
type: 'FeatureCollection',
features: lineContours.map(p => ({
type: 'Feature',
properties: {
value: p.value,
color: colorScale(p.value),
label: `${p.value}`
},
geometry: {
type: 'MultiPolygon',
coordinates: p.coordinates
.map(ring =>
ring.map(coords =>
coords
.map(point => ([
minLng + (maxLng - minLng) * (point[0] / width),
maxLat - (maxLat - minLat) * (point[1] / height)
]))
.map(point => ([
point[0],
point[1] > 90 ? point[1] - 90 : point[1],
]))
)
)
}
})),
};
map.addSource('wave-polys', {
type: 'geojson',
data: polygonFeatureCollection,
});
map.addLayer({
id: 'wave-fill',
type: 'fill',
source: 'wave-polys',
layout: {},
opacity: 0.75,
paint: {
'fill-color': ['get', 'color']
}
});
map.addSource('wave-lines', {
type: 'geojson',
data: lineFeatureCollection,
});
map.addLayer({
id: 'wave-lines',
type: 'line',
source: 'wave-lines',
layout: {
'line-cap': 'round',
'line-join': 'round',
},
paint: {
'line-color': '#000000',
'line-width': 2,
},
});
map.addLayer({
id: 'wave-lines-labels',
source: 'wave-lines',
type: 'symbol',
layout: {
'visibility': 'visible',
'symbol-placement': 'line',
'text-field': ['get', 'label'],
'text-letter-spacing': 0.2,
'text-line-height': 1.0,
// 'text-max-angle': 10,
"text-font": ["Open Sans Bold", "Arial Unicode MS Bold"],
'text-offset': [0, 1],
'text-rotation-alignment': 'map'
},
paint: {
'icon-color': '#000000',
'icon-halo-width': 1,
'text-color': '#000000',
'text-halo-color': '#ffffff',
'text-halo-width': 0.0,
}
})
})
@mpiannucci
Copy link
Author

Screenshot 2022-12-15 at 12 10 35 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment