Skip to content

Commit 22e428e

Browse files
authored
Merge pull request #3 from geographika/stac
Add new STAC tutorial
2 parents 4e787d1 + fef58fa commit 22e428e

File tree

6 files changed

+272
-8
lines changed

6 files changed

+272
-8
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# Working with STAC - SpatioTemporal Asset Catalogs
2+
3+
## Overview
4+
5+
MapServer can display data directly from a STAC server using GDAL's [STACIT driver](https://gdal.org/en/latest/drivers/raster/stacit.html).
6+
7+
In this example, we will build on the [Download data from a STAC API using GDAL and the command line](https://stacspec.org/en/tutorials/gdal_cli/) tutorial,
8+
and demonstrate how to render data from Microsoft's Planetary Computer using a STAC connection.
9+
10+
<div class="map">
11+
<iframe src="https://mapserver.github.io/getting-started-with-mapserver-demo/stac.html"></iframe>
12+
</div>
13+
14+
## Checking the Data using GDAL
15+
16+
First, let's inspect the data using GDAL command-line tools. Since GDAL is already installed in the MapServer Docker container,
17+
we can connect to the container and run the necessary commands:
18+
19+
```bash
20+
# open a bash shell
21+
docker exec -it mapserver /bin/bash
22+
# check the GDAL version
23+
gdal --version
24+
# check we have the STACIT driver
25+
gdal vector --formats | grep STACIT
26+
# STACIT -raster- (rovs): Spatio-Temporal Asset Catalog Items
27+
```
28+
29+
First, let's retrieve information about the 2021 LCMAP primary land cover classification (`lcpri`) asset using GDAL:
30+
31+
```bash
32+
gdal raster info "STACIT:\"https://planetarycomputer.microsoft.com/api/stac/v1/search?&collections=usgs-lcmap-conus-v13&datetime=2021-01-01/2021-12-31\":asset=lcpri" --output-format text
33+
```
34+
35+
We can also provide a bounding box to inspect the file names and details for a specific area:
36+
37+
```bash
38+
gdal raster info "STACIT:\"https://planetarycomputer.microsoft.com/api/stac/v1/search?&collections=usgs-lcmap-conus-v13&datetime=2021-01-01/2021-12-31&bbox=-73.94658,41.95589,-73.88281,42.00546\":asset=lcpri" --output-format text
39+
```
40+
41+
To get statistics for the raster data in the selected area, use the `--stats` flag:
42+
43+
```bash
44+
gdal raster info "STACIT:\"https://planetarycomputer.microsoft.com/api/stac/v1/search?&collections=usgs-lcmap-conus-v13&datetime=2021-01-01/2021-12-31&bbox=-73.94658,41.95589,-73.88281,42.00546\":asset=lcpri" --stats
45+
```
46+
47+
At the end of the output, GDAL lists the bands and their data types. We will use this information later when writing our Mapfile.
48+
49+
```json
50+
"raster:bands":[
51+
{
52+
"data_type":"uint8",
53+
"stats":{
54+
"minimum":1.0,
55+
"maximum":8.0,
56+
"mean":3.475,
57+
"stddev":1.443
58+
},
59+
"nodata":0
60+
}
61+
],
62+
```
63+
64+
When inspecting the dataset with GDAL, `null` is returned for the `proj:epsg` value. Looking at the `projjson` property, the conversion method is listed as "Albers Equal Area".
65+
This means the dataset uses a custom projection without an official EPSG code. While MapServer can handle custom projections, for the purposes of this workshop we will use
66+
[EPSG:5070](https://spatialreference.org/ref/epsg/5070/) in our Mapfile. EPSG:5070 corresponds to NAD83 / Conus Albers,
67+
a standard Albers Equal Area implementation for the United States, which works well with this dataset.
68+
69+
```json
70+
"proj:epsg":null,
71+
"proj:projjson":{
72+
"$schema":"https://proj.org/schemas/v0.7/projjson.schema.json",
73+
"type":"ProjectedCRS",
74+
"name":"AEA WGS84",
75+
"base_crs":{
76+
"type":"GeographicCRS",
77+
"name":"WGS 84",
78+
...
79+
},
80+
"id":{
81+
"authority":"EPSG",
82+
"code":4326
83+
}
84+
...
85+
"conversion":{
86+
"name":"unnamed",
87+
"method":{
88+
"name":"Albers Equal Area",
89+
"id":{
90+
"authority":"EPSG",
91+
"code":9822
92+
}
93+
},
94+
```
95+
96+
97+
## The Mapfile
98+
99+
In this Mapfile, we use the `BBOX` parameter sent by WMS clients (such as OpenLayers) to dynamically update the `DATA` clause. We accomplish this using
100+
[runtime substitution](https://mapserver.org/cgi/runsub.html) and by defining the `BBOX` parameter in the `WEB` `VALIDATION` block.
101+
A regular expression is used to ensure that the parameter is in the expected format.
102+
103+
```scala
104+
WEB
105+
VALIDATION
106+
# check the parameter is in the form -74.134,41.871,-73.694,42.089
107+
BBOX '^-?\d+(?:\.\d+)?,-?\d+(?:\.\d+)?,-?\d+(?:\.\d+)?,-?\d+(?:\.\d+)?$'
108+
END
109+
```
110+
111+
Whenever a `BBOX` parameter is passed to MapServer in a querystring, it becomes available as the dynamic variable `%BBOX%` in the Mapfile.
112+
113+
In this example, we use `%BBOX% ` to dynamically update the STAC `bbox` parameter, allowing the Mapfile to request only the data for the area currently viewed by the WMS client.
114+
115+
```scala
116+
LAYER
117+
NAME "lcpri"
118+
TYPE RASTER
119+
DATA 'STACIT:\"https://planetarycomputer.microsoft.com/api/stac/v1/search?&collections=usgs-lcmap-conus-v13&datetime=2021-01-01/2021-12-31&bbox=%BBOX%\":asset=lcpri'
120+
```
121+
122+
In the OpenLayers client, we use WMS 1.1.1 because its `BBOX` coordinate order matches the longitude/latitude order used by the STAC `bbox` parameter.
123+
124+
By contrast, WMS 1.3.0 uses a latitude/longitude order for its BBOX when using EPSG:4326, which can cause coordinate mismatches if used directly with STAC queries.
125+
126+
```js
127+
new ImageLayer({
128+
source: new ImageWMS({
129+
url: mapserverUrl + mapfilesPath + 'stac.map&',
130+
params: { 'LAYERS': 'lcpri', 'STYLES': '', VERSION: '1.1.1' }
131+
}),
132+
```
133+
134+
## Code
135+
136+
!!! example
137+
138+
- Local OpenLayers example: <http://localhost:7001/stac.html>
139+
140+
??? JavaScript "stac.js"
141+
142+
``` js
143+
--8<-- "stac.js"
144+
```
145+
146+
??? Mapfile "stac.map"
147+
148+
``` scala
149+
--8<-- "stac.map"
150+
```
151+
152+
## Exercises
153+
154+
- The [metadata](https://data.usgs.gov/datacatalog/metadata/USGS.6759c005d34edfeb8710a490.xml) provides what pixel values represent in the dataset.
155+
Add new classes to the Mapfile to represent each of the 8 landcover types.
156+
157+
```xml
158+
<edom>
159+
<edomv>3</edomv>
160+
<edomvd>
161+
Grass/Shrub. Land predominantly covered with shrubs and perennial or annual natural and domesticated grasses (e.g., pasture),
162+
forbs, or other forms of herbaceous vegetation. The grass and shrub cover must comprise at least 10% of the area and tree cover is less than 10% of the area.
163+
</edomvd>
164+
<edomvds>LCMAP Legend Land Cover Class Descriptions</edomvds>
165+
</edom>
166+
```
167+
168+
- Comment-out the classes and uncomment the `PROCESSING` directives to create a black and white output of the land cover dataset.
169+
170+
## Further Links
171+
172+
- https://planetarycomputer.microsoft.com/docs/quickstarts/reading-stac/

workshop/content/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ nav:
3232
- Vector Symbols: advanced/symbols.md
3333
- Clusters: advanced/clusters.md
3434
- SLD: advanced/sld.md
35+
- STAC: advanced/stac.md
3536
# - Apache: advanced/apache.md
3637
# - MapScript: advanced/mapscript.md
3738
- Summary: summary.md

workshop/exercises/app/index.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,35 @@ <h1>Getting Started with MapServer</h1>
1313
<h2>Mapfile</h2>
1414
<ul>
1515
<li><a href="lines.html">Lines</a></li>
16-
<li><a href="points.html">Points</a></li>
16+
<li><a href="points.html">Points</a></li>
1717
<li><a href="lakes.html">Labels (Lakes)</a></li>
18-
<li><a href="polygons.html">Polygons</a></li>
18+
<li><a href="polygons.html">Polygons</a></li>
1919
</ul>
2020
<h2>Inputs</h2>
2121
<ul>
2222
<li><a href="raster.html">Raster</a></li>
23-
<li><a href="stars.html">Stars (Vector)</a></li>
24-
<li><a href="postgis.html">PostGIS (Databases)</a></li>
23+
<li><a href="stars.html">Stars (Vector)</a></li>
24+
<li><a href="postgis.html">PostGIS (Databases)</a></li>
2525
</ul>
2626
<h2>Outputs</h2>
2727
<ul>
2828
<li><a href="wfs.html">WFS</a></li>
2929
<li><a href="tiles.html">Tiles Mode</a></li>
3030
<li><a href="vector-tiles.html">Vector Tiles</a></li>
31-
<li><a href="ogcapi-features.html">OGC API - Features</a></li>
31+
<li><a href="ogcapi-features.html">OGC API - Features</a></li>
3232
</ul>
3333
<h2>Advanced</h2>
3434
<ul>
35-
<li><a href="railways.html">Vector Symbols (Railways)</a></li>
35+
<li><a href="railways.html">Vector Symbols (Railways)</a></li>
3636
<li><a href="clusters.html">Clusters</a></li>
3737
<li><a href="landuse.html">Landuse</a></li>
3838
<li><a href="contours.html">Contours</a></li>
3939
<li><a href="sld.html">SLD - Styled Layer Descriptor</a></li>
40-
40+
<li><a href="stac.html">STAC</a></li>
4141
</ul>
4242
<h2>Miscellaneous</h2>
4343
<ul>
44-
<li><a href="fonts.html">Material Icon Symbols</a></li>
44+
<li><a href="fonts.html">Material Icon Symbols</a></li>
4545
</ul>
4646
<script type="module" src="./js/index.js"></script>
4747
</body>

workshop/exercises/app/js/stac.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import '../css/style.css';
2+
import ImageWMS from 'ol/source/ImageWMS.js';
3+
import Map from 'ol/Map.js';
4+
import View from 'ol/View.js';
5+
import { Image as ImageLayer } from 'ol/layer.js';
6+
7+
const mapserverUrl = import.meta.env.VITE_MAPSERVER_BASE_URL;
8+
const mapfilesPath = import.meta.env.VITE_MAPFILES_PATH;
9+
10+
const layers = [
11+
new ImageLayer({
12+
source: new ImageWMS({
13+
url: mapserverUrl + mapfilesPath + 'stac.map&',
14+
params: { 'LAYERS': 'lcpri', 'STYLES': '', VERSION: '1.1.1' }
15+
}),
16+
}),
17+
];
18+
const map = new Map({
19+
layers: layers,
20+
target: 'map',
21+
view: new View({
22+
projection: 'EPSG:4326',
23+
center: [-73.914695, 41.980675],
24+
zoom: 13,
25+
}),
26+
});

workshop/exercises/app/stac.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/x-icon" href="https://openlayers.org/favicon.ico" />
6+
<link rel="stylesheet" href="node_modules/ol/ol.css">
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
8+
<title>STAC</title>
9+
</head>
10+
<body>
11+
<div id="map"></div>
12+
<script type="module" src="./js/stac.js"></script>
13+
</body>
14+
</html>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
MAP
2+
NAME "STAC"
3+
EXTENT -180 -90 180 90
4+
UNITS DD
5+
SIZE 600 600
6+
PROJECTION
7+
"init=epsg:4326"
8+
END
9+
WEB
10+
METADATA
11+
"ows_enable_request" "*"
12+
"ows_srs" "EPSG:4326 EPSG:3857 EPSG:5070"
13+
END
14+
VALIDATION
15+
# check the parameter is in the form -74.134,41.871,-73.694,42.089
16+
BBOX '^-?\d+(?:\.\d+)?,-?\d+(?:\.\d+)?,-?\d+(?:\.\d+)?,-?\d+(?:\.\d+)?$'
17+
END
18+
END
19+
20+
LAYER
21+
NAME "lcpri"
22+
TYPE RASTER
23+
DATA 'STACIT:\"https://planetarycomputer.microsoft.com/api/stac/v1/search?&collections=usgs-lcmap-conus-v13&datetime=2021-01-01/2021-12-31&bbox=%BBOX%\":asset=lcpri'
24+
PROJECTION
25+
"init=epsg:5070"
26+
END
27+
PROCESSING "BANDS=1"
28+
CLASS
29+
EXPRESSION ([pixel] = 5)
30+
STYLE
31+
COLOR 28 107 160
32+
END
33+
END
34+
35+
CLASS
36+
EXPRESSION ([pixel] != 5)
37+
STYLE
38+
COLORRANGE 255 255 230 0 100 0 # light green to dark green
39+
DATARANGE 1 8
40+
END
41+
END
42+
43+
# to switch to black and white uncomment below
44+
45+
# PROCESSING "SCALE=1,8"
46+
# PROCESSING "SCALE_BUCKETS=256"
47+
# PROCESSING "CLASSIFY_SCALED=TRUE"
48+
49+
END
50+
51+
END

0 commit comments

Comments
 (0)