-
Notifications
You must be signed in to change notification settings - Fork 449
/
Create isobands from isolines.html
211 lines (177 loc) · 10.4 KB
/
Create isobands from isolines.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
<!DOCTYPE html>
<html lang="en">
<head>
<title>Create isobands from isolines - Azure Maps Web SDK Samples</title>
<meta charset="utf-8" />
<link rel="shortcut icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="This sample shows how to create isobands from isoline data and display them on a map with a data driven styling to assign colors. Isobands represent the areas between isolines." />
<meta name="keywords" content="Microsoft maps, map, gis, API, SDK, line, linestring, polyline, layer, isobands, isoband, isolines, isoline, contour, linelayer, data-driven, data driven styling, earthquakes, USGS" />
<meta name="author" content="Microsoft Azure Maps" /><meta name="version" content="1.0" />
<meta name="screenshot" content="screenshot.jpg" />
<!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
<link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.css" rel="stylesheet" />
<script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/3/atlas.min.js"></script>
<!-- Load turf.js a spatial math library. https://turfjs.org/ -->
<script src='/lib/turf.min.js'></script>
<script>
var map, datasource;
//Earthquake intensity contours of M7.0 – 1km WSW of Kumamoto-shi, Japan (GeoJson source from usgs.gov: https://earthquake.usgs.gov/data/shakemap/)
var contourLineDataUrl = 'https://earthquake.usgs.gov/product/shakemap/us20005iis/us/1467057010522/download/cont_psa03.json';
function getMap() {
//Initialize a map instance.
map = new atlas.Map('myMap', {
center: [131.074968, 32.944332],
zoom: 8,
style: 'grayscale_light',
view: 'Auto',
//Add authentication details for connecting to Azure Maps.
authOptions: {
//Use Microsoft Entra ID authentication.
authType: 'anonymous',
clientId: 'e6b6ab59-eb5d-4d25-aa57-581135b927f0', //Your Azure Maps client id for accessing your Azure Maps account.
getToken: function (resolve, reject, map) {
//URL to your authentication service that retrieves an Microsoft Entra ID Token.
var tokenServiceUrl = 'https://samples.azuremaps.com/api/GetAzureMapsToken';
fetch(tokenServiceUrl).then(r => r.text()).then(token => resolve(token));
}
//Alternatively, use an Azure Maps key. Get an Azure Maps key at https://azure.com/maps. NOTE: The primary key should be used as the key.
//authType: 'subscriptionKey',
//subscriptionKey: '[YOUR_AZURE_MAPS_KEY]'
}
});
//Wait until the map resources are ready.
map.events.add('ready', function () {
//Create a data source and add it to the map.
datasource = new atlas.source.DataSource();
map.sources.add(datasource);
//Create a layer to render polygon data.
map.layers.add(new atlas.layer.PolygonLayer(datasource, null, {
//Use a data-driven expression based on a property on each polygon to assign a color.
fillColor: [
'step',
['get', 'value'],
'transparent', //If value is less than 40, make it transparent.
40, 'rgb(25, 150, 65)', //40 - 80 color
80, 'rgb(140, 202, 32)', //80 - 120 color
120, 'rgb(255, 255, 0)', //120 - 160 color
160, 'rgb(235, 140, 14)', //160 - 200 color
200, 'rgb(215, 25, 28)' //200+ color
],
fillOpacity: 0.5
}), 'labels');
//Load data that represents contour lines.
fetch(contourLineDataUrl).then(r => r.json()).then(fc => {
//Convert the isolines into isobands. Sort based on the "value" property.
var isobands = createIsobandsFromIsolines(fc.features, 'value');
//Add the polygons to the data source.
datasource.add(isobands);
});
});
}
//Takes an array of isolines and converts them to isobands.
function createIsobandsFromIsolines(isolines) {
//Optionally sort features by property value. In this case the "value" property.
sortFeaturesByProperty(isolines, 'elevation');
//Create polygons from the isolines.
var polygons = [];
isolines.forEach(f => {
polygons.push(convertIsolineToPolygon(f));
});
//Step through each polygon and calculate the difference with all remaining polygons.
var isobands = [];
for (var i = 0; i < polygons.length; i++) {
var p = polygons[i];
for (var j = i + 1; j < polygons.length; j++) {
p = turf.difference(p, polygons[j]);
//If one polygon is completely overlapped, there will be nothing left, and thus be null.
if (p === null) {
break;
}
}
if (p !== null) {
isobands.push(p);
}
}
return isobands;
}
//Creates a sorting function for a GeoJSON property.
function sortFeaturesByProperty(features, propertyName) {
features.sort(function (a, b) {
return a.properties[propertyName] - b.properties[propertyName];
});
}
//Convert an Isoline into a polygon.
function convertIsolineToPolygon(feature) {
//Create polygons from the line data.
if (feature.geometry.type.indexOf('LineString') > -1) {
if (feature.geometry.type === 'LineString') {
feature.geometry.type = 'Polygon';
//Line represents a single polygon ring.
var ring = [feature.geometry.coordinates];
closeRings(ring);
feature.geometry.coordinates = ring;
} else {
//Feature is a MultiLineString
feature.geometry.type = 'MultiPolygon';
//A MultiLineString represents multiple individual polygons, each with one ring.
var polygonRings = [];
feature.geometry.coordinates.forEach(ring => {
closeRings(ring);
polygonRings.push([ring]);
});
feature.geometry.coordinates = polygonRings;
}
}
return feature;
}
//Ensures that all coordinate rings in a polygon are closed.
function closeRings(polygonCoordinates) {
var lastIdx;
polygonCoordinates.forEach(ring => {
lastIdx = ring.length - 1;
//Check to see if the first and last coordinates in the ring are the same. If not, add the first coordinate to the end.
if (!atlas.data.Position.areEqual(ring[0], ring[lastIdx])) {
ring.push(ring[0]);
}
});
}
</script>
<style>
.colorband {
width: 100px;
padding: 2px;
text-align: center;
}
</style>
</head>
<body onload="getMap()">
<div id="myMap" style="position:relative;width:100%;min-width:290px;height:600px;"></div>
<div style="position:absolute;top:10px;left:10px;background-color:#eee;border-radius:10px;padding:10px;">
<div class="colorband" style="background-color: rgba(215, 25, 28, 0.5)">200%g</div>
<div class="colorband" style="background-color: rgba(235, 140, 14, 0.5)">160%g</div>
<div class="colorband" style="background-color: rgba(255, 255, 0, 0.5)">120%g</div>
<div class="colorband" style="background-color: rgba(140, 202, 32, 0.5)">80%g</div>
<div class="colorband" style="background-color: rgba(25, 150, 65, 0.5)">40%g</div>
</div>
<fieldset style="width:calc(100% - 30px);min-width:290px;margin-top:10px;">
<legend>Create isobands from isolines</legend>
This sample shows how to create isobands from isoline data and display them on a map with a data driven styling to assign colors. Isobands represent the areas between isolines.
Isoline data often consists of LineString or MultiLineString shapes that represent closed rings. The lines should never overlap.
<br /><br />
There are a couple of methods that can be used to create isobands depending on the format of the data.
You can assume the data is already sorted is ascending order, or you can sort the data by a property value in ascending order.
If you will be using solid colors, you can simply add the polygons as is to the map. If not, then you will need to cut out overlapping areas using one of the methods below.
<ul>
<li>Convert the line data into polygons, then take first polygon and calculate the difference compared to all other (subtract second polygon area from first one), higher value, polygons, then go to the second polygon and do the same.</li>
<li>If the data only consists of LineStrings, then each new line should be within the previous lines area, thus you can use one line as the first ring of a polygon and the second line as hole within the polygon. This is simple but error prone.</li>
</ul>
<br />
This sample shows how to do the first method.
Note that LineStrings can be converted to Polygons, and MultiLineStrings into MultiPolygons by simply wrapping the coordinates within an array. From there, one of the following methods can be used.
This sample uses the open source <a href="https://turfjs.org/" target="_blank">Turf.js</a> library to perform the difference calculation.
<br />
The data in this sample represents Earthquake intensity contours of M7.0 – 1km WSW of Kumamoto-shi, Japan and is sourced from the <a href="https://earthquake.usgs.gov/data/shakemap/">USGS Earthquake Hazards Program</a>.
</fieldset>
</body>
</html>