block by ajzeigert f7acf1abb46f581c1192

Data-driven styling example in Mapbox GL JS

Full Screen

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8' />
    <title>GAS PIRATE</title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.11.1/mapbox-gl.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.11.1/mapbox-gl.css' rel='stylesheet' />
		<script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
		<script src="//cdn-geoweb.s3.amazonaws.com/terraformer/1.0.4/terraformer.min.js"></script>
		<script src="//cdn-geoweb.s3.amazonaws.com/terraformer-arcgis-parser/1.0.4/terraformer-arcgis-parser.min.js"></script>
<!--		<script src='colorbrewer.js'></script>-->
	<style>
		
		html, body {
			font-family: sans-serif;
			padding: 0;
			margin: 0;
		}
		
		p {
			margin: 0;
		}
		
		#map {
			width: 100vw;
			height: 100vh;
		}

		#timeslider {
			background: rgba(255,255,255,0.5);
			position: absolute;
			right: 10px;
			top: 10px;
			padding: 10px;
			width: 400px;
		}
		
		#timeslider input {
			width: 100%;
		}
		
		#timeslider h3, h4 {
			margin: 0;
		}
		
	</style>
</head>
<body>
	<div id="map"></div>
	<div id="timeslider">
		<h4>Slide to select 3 nearest readings to selected time</h4>
		<input type='range' min=0 max=100 step=10>
		<h3>Selected time</h3>
	</div>
	<script>
		
		// Smartmine mapbox token
		mapboxgl.accessToken = 'pk.eyJ1Ijoic21hcnRtaW5lIiwiYSI6Imt6TUp0cEEifQ.9MrwD6GRlEGj9OTNN-bsRw';
		
		// Smartmine map style
		var geodark = 'mapbox://styles/smartmine/cigdv27lk00029hkt6leqc9yz';
		
		// Fire up the map
		var map = new mapboxgl.Map({
				container: 'map', // container id
				style: geodark, //stylesheet location
				center: [-118.25, 34.05], // starting position
				zoom: 10, // starting zoom
				minzoom: 10,
				hash: true
		});
		
		// Add a new basic controller
		map.addControl(new mapboxgl.Navigation({position: 'top-left'}));
				
		// After the map style loads, do this stuff
		map.on('style.load', function() {
			
			// Add the high-pressure line source
			map.addSource("high-pressure", {
				"type": 'geojson',
				"data": 'pipeline_hp.json'
			});
			
			// Add the high-pressure line style
			map.addLayer({
				"id": 'high-pressure',
				"type": 'line',
				"source": 'high-pressure',
				"layout": {
					"line-join": 'round',
					"line-cap": 'round',
				},
				"paint": {
					"line-color": "#7aa0b4",
					"line-width": '4',
					"line-blur": 3
				}
			});			
			
			// Add the transmission line source
			map.addSource("transmission", {
				"type": 'geojson',
				"data": 'pipeline_t.json'
			});
			
			// Add the transmission line style
			map.addLayer({
				"id": 'transmission',
				"type": 'line',
				"source": 'transmission',
				"layout": {
					"line-join": 'round',
					"line-cap": 'round',
				},
				"paint": {
					"line-color": "#013f62",
					"line-width": '4',
					"line-blur": 3
				}
			});
						
			$.ajax({
				url: '//tmservices1.esri.com/arcgis/rest/services/LiveFeeds/NOAA_METAR_current_wind_speed_direction/MapServer/0/query?where=objectId%3E%3D0&text=&objectIds=&time=&geometry=-114%2C32%2C-120%2C36&geometryType=esriGeometryEnvelope&inSR=4326&spatialRel=esriSpatialRelEnvelopeIntersects&relationParam=&outFields=*&returnGeometry=true&maxAllowableOffset=&geometryPrecision=&outSR=4326&returnIdsOnly=false&returnCountOnly=false&orderByFields=&groupByFieldsForStatistics=&outStatistics=&returnZ=false&returnM=false&gdbVersion=&returnDistinctValues=false&f=json',
				dataType: 'json'})
			.done(function(windresponse){
								
				var FeatureCollection = {
					type: "FeatureCollection",
					features: []
				}
				
				for(var i = 0; i < windresponse.features.length; i++){
					var feature = Terraformer.ArcGIS.parse(windresponse.features[i]);
					feature.id = i;
					FeatureCollection.features.push(feature);
				}
								
//				 Add the newly created geojson as a source	
				map.addSource('wind', {
					"type": 'geojson',
					"data": FeatureCollection
				});
				
				// Function to translate wind speed to proper text size
				var windSize = function(feature){
					var a = feature.properties.WIND_SPEED
					if (a > 0 && a <= 11 ) {
						return 12;
					}
					else if (a > 11 && a <= 20) {
						return 14;
					}
					else if (a > 20 && a <= 28) {
						return 16;
					}
					else if (a > 28 && a <= 39) {
						return 18;
					}
					else if (a > 39 && a <= 94) {
						return 20;
					}
					else {
						return 0;
					}
				}
				
				// Function to translate wind speed to proper text color
				var windColor = function(feature){
					var a = feature.properties.WIND_SPEED
					if (a > 0 && a <= 11 ) {
						return 'rgb(191, 184, 64)';
					}
					else if (a > 11 && a <= 20) {
						return 'rgb(242, 203, 103)';
					}
					else if (a > 20 && a <= 28) {
						return 'rgb(237, 162, 62)';
					}
					else if (a > 28 && a <= 39) {
						return 'rgb(219, 94, 49)';
					}
					else if (a > 39 && a <= 94) {
						return 'rgb(168, 0, 0)';
					}
					else {
						return 'rgb(255,255,255)';
					}
				}
								
				// Build an array of layers for each features
				var windLayers = [];
				
				// Build a layer for each feature and push it into the empty array
				var i;
				
				for (i = 0; i < FeatureCollection.features.length; i++) {
					var layer = {
						"id": 'wind' + i,
						"type": 'symbol',
						"source": 'wind',
						"layout": {
							"text-field": FeatureCollection.features[i].properties.WIND_SPEED > 0 ? 'E' : '5',
							"text-font": ['ESRI Dimensioning Regular'],
							"text-rotate": FeatureCollection.features[i].properties.WIND_DIRECT,
							"text-rotation-alignment": 'map',
							"text-size": windSize(FeatureCollection.features[i])
						},
						"paint": {
							"text-color": windColor(FeatureCollection.features[i])
						},
						"filter": ['==', 'OBJECTID', FeatureCollection.features[i].properties.OBJECTID]
					};
					
					windLayers.push(layer);
				}
				
				console.log(windLayers)
				
				map.batch(function(batch){
					var i;
					for (i = 0; i < windLayers.length; i++) {
						batch.addLayer(windLayers[i])
					}
				})
			
			}); // End of esri call

						
			// Load the readings geojson using ajax, because we want to manipulate the object before handing to mapbox gl js
			
			// Empty readings object so it's available outside the ajax context
			var readings = {};
			
			// Load the geojson, and execute a function on it when done
			$.ajax({
				url: 'readings.geojson',
				dataType: 'json'})
				.done(function(data){
				
//				console.log(data);
				
					// Set readings to the data from geojson
					readings = data;
					
					// Since this geojson has no object ID, lets make one
					for (i = 0; i < readings.features.length; i++){
						readings.features[i].properties.readingID = i;
					}
					
					// Sort readings by sensor time
					readings.features.sort(function(a,b){
						a = new Date(a.properties.sensorTime);
						b = new Date(b.properties.sensorTime);
						return a>b ? 1 : a<b ? -1 : 0;
					});
									
					// Declare oldest and newest reading
					var oldestReading = new Date(readings.features[0].properties.sensorTime);
					var newestReading = new Date(readings.features[readings.features.length - 1].properties.sensorTime);
				
					// Add our new geojson as a source
					map.addSource("readings", {
						"type": 'geojson',
						"data": readings
					});

					// Because as of v8, there is no support in Mapbox GL js for data-driven styling, 
					// this code creates new layers for each "style" and filters the appropriate symbols
				
					// Add a new layer for gray symbols and filter based on LEL readings
					map.addLayer({
						"id": 'readings-normal',
						"type": 'circle',
						"source": 'readings',
						"paint": {
							"circle-radius": 8,
							"circle-color": '#d8d8d8',
							"circle-opacity": 0.5
						},
						"filter": ['<=', 'percentLEL', 100],
						"interactive": 'true'
					});
				
					// Add a new layer for red symbols and filter based on LEL readings
					map.addLayer({
						"id": 'readings-warning',
						"type": 'circle',
						"source": 'readings',
						"paint": {
							"circle-radius": 12,
							"circle-color": '#F03B20',
							"circle-opacity": 0.75
						},
						"filter": ['>=', 'percentLEL', 100],
						"interactive": 'true'
					});
				
					// Add a new layer for item labels using the readings source
					map.addLayer({
						"id": 'readings-label',
						"type": 'symbol',
						"source": 'readings',
						"layout": {
							"text-field": 'Device ID: {deviceID}',
							"text-anchor": 'bottom-left',
							"text-size": 14,
							"text-font": ['Open Sans Bold'],
							"text-offset": [0.5,-0.5]
						},
						"paint": {
							'text-color': '#fff'
						}
					})
				
				
					// Set the range input min and max to the oldest and newest readings
					$('#timeslider input').attr('min', Date.parse(oldestReading)).attr('max', Date.parse(newestReading));
					
					// When there's a change event on the slider...
					$('#timeslider input').on('change', function(){
						
						// Grab the value of the slider
						var displayTime = $('#timeslider input')[0].value;
						
						// Make displayTime a date object
						displayTime = new Date(+displayTime);
						
						// Put that date into the slider box
						$('#timeslider h3').html(displayTime);
						
						// Gimme the raw javascript time
						var compareTime  = Date.parse(displayTime);
						
						// Check all dates in the features against the selected time note the difference
						for (i = 0; i < readings.features.length; i++){
							var sensorTime = new Date(readings.features[i].properties.sensorTime);
							var difference = Math.abs(compareTime - sensorTime);
							readings.features[i].properties.difference = difference;
						}
						// Re-sort the readings based on the shortest difference
						readings.features.sort(function(a,b){
							a = a.properties.difference;
							b = b.properties.difference;
							return a>b ? 1 : a<b ? -1 : 0;
						});
						
						// Create three filter sets for the three readings layers (including labels)
						// Only show the top three features, which should have the lowest difference
						var filtersNormal = [
							'all',
							['in', 'readingID', readings.features[0].properties.readingID, readings.features[1].properties.readingID, readings.features[2].properties.readingID],
							['<=', 'percentLEL', 100]
							];
						
						var filtersWarning = [
							'all',
							['in', 'readingID', readings.features[0].properties.readingID, readings.features[1].properties.readingID, readings.features[2].properties.readingID],
							['>=', 'percentLEL', 100]
							];
						
						var filtersLabel = ['in', 'readingID', readings.features[0].properties.readingID, readings.features[1].properties.readingID, readings.features[2].properties.readingID];
						
						// Update the layers with the new filters
						map.setFilter('readings-warning', filtersWarning);
						map.setFilter('readings-normal', filtersNormal);
						map.setFilter('readings-label', filtersLabel);
						
					}); // End input on change function
				
				}); // End ajax done function
			
			// Get the feature info when clicked
			map.on('click', function (e) {
				map.featuresAt(e.point, {radius: 5, layer: ['readings-warning', 'readings-normal']}, function (err, features) {
					if (err) throw err;
					console.log(features[0]);
					var popup = new mapboxgl.Popup()
						.setLngLat([features[0].properties.lon, features[0].properties.lat]) //
						.setHTML('<p><strong>Percent LEL: </strong>' + features[0].properties.percentLEL + '</p>'
										+ '<p><strong>Temp (C): </strong>' + features[0].properties.temp + '</p>')
						.addTo(map)
				});
			});
			
			}); // End mapbox on load function

	</script>
</body>
</html>

readings.geojson

{
"type": "FeatureCollection",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
                                                                                
"features": [
{ "type": "Feature", "properties": { "deviceID": 39399456, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 51.100000, "temp": 21, "messageTime": "7\/23\/2015 12:01:06 PM", "sensorTime": "7\/23\/2015 12:01:14 PM", "lon": -118.183979, "lat": 33.929859 }, "geometry": { "type": "Point", "coordinates": [ -118.1839791, 33.92985899 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399665, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 179.600000, "temp": 19, "messageTime": "9\/27\/2015 9:59:36 AM", "sensorTime": "9\/27\/2015 10:01:28 AM", "lon": -118.204994, "lat": 33.847322 }, "geometry": { "type": "Point", "coordinates": [ -118.2049942, 33.84732249 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399665, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 37.200000, "temp": 19, "messageTime": "9\/27\/2015 9:29:36 AM", "sensorTime": "9\/27\/2015 9:31:28 AM", "lon": -118.204994, "lat": 33.847322 }, "geometry": { "type": "Point", "coordinates": [ -118.2049942, 33.84732249 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399665, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 15.900000, "temp": 18, "messageTime": "9\/27\/2015 8:59:36 AM", "sensorTime": "9\/27\/2015 9:01:28 AM", "lon": -118.204994, "lat": 33.847322 }, "geometry": { "type": "Point", "coordinates": [ -118.2049942, 33.84732249 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399665, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 469.900000, "temp": 18, "messageTime": "8\/12\/2015 10:00:23 AM", "sensorTime": "8\/12\/2015 10:01:26 AM", "lon": -118.204994, "lat": 33.847322 }, "geometry": { "type": "Point", "coordinates": [ -118.2049942, 33.84732249 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399741, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 93.700000, "temp": 16, "messageTime": "9\/27\/2015 10:16:50 AM", "sensorTime": "9\/27\/2015 10:16:04 AM", "lon": -118.222160, "lat": 33.804684 }, "geometry": { "type": "Point", "coordinates": [ -118.2221603, 33.80468372 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399741, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 16.700000, "temp": 15, "messageTime": "9\/27\/2015 10:01:50 AM", "sensorTime": "9\/27\/2015 10:01:04 AM", "lon": -118.222160, "lat": 33.804684 }, "geometry": { "type": "Point", "coordinates": [ -118.2221603, 33.80468372 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399927, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 35.600000, "temp": 13, "messageTime": "10\/5\/2015 9:59:03 AM", "sensorTime": "10\/5\/2015 10:01:22 AM", "lon": -118.326874, "lat": 33.943360 }, "geometry": { "type": "Point", "coordinates": [ -118.3268738, 33.94335995 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399927, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 16.100000, "temp": 13, "messageTime": "10\/5\/2015 7:59:03 AM", "sensorTime": "10\/5\/2015 8:01:22 AM", "lon": -118.326874, "lat": 33.943360 }, "geometry": { "type": "Point", "coordinates": [ -118.3268738, 33.94335995 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399927, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 122.100000, "temp": 19, "messageTime": "9\/27\/2015 10:59:12 AM", "sensorTime": "9\/27\/2015 11:01:22 AM", "lon": -118.326874, "lat": 33.943360 }, "geometry": { "type": "Point", "coordinates": [ -118.3268738, 33.94335995 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399927, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 26.000000, "temp": 18, "messageTime": "9\/27\/2015 9:59:12 AM", "sensorTime": "9\/27\/2015 10:01:22 AM", "lon": -118.326874, "lat": 33.943360 }, "geometry": { "type": "Point", "coordinates": [ -118.3268738, 33.94335995 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399927, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 536.400000, "temp": 21, "messageTime": "9\/15\/2015 11:59:26 AM", "sensorTime": "9\/15\/2015 12:01:22 PM", "lon": -118.326874, "lat": 33.943360 }, "geometry": { "type": "Point", "coordinates": [ -118.3268738, 33.94335995 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399927, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 305.000000, "temp": 20, "messageTime": "9\/15\/2015 11:44:26 AM", "sensorTime": "9\/15\/2015 11:46:22 AM", "lon": -118.326874, "lat": 33.943360 }, "geometry": { "type": "Point", "coordinates": [ -118.3268738, 33.94335995 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399927, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 18.100000, "temp": 19, "messageTime": "9\/15\/2015 10:59:26 AM", "sensorTime": "9\/15\/2015 11:01:22 AM", "lon": -118.326874, "lat": 33.943360 }, "geometry": { "type": "Point", "coordinates": [ -118.3268738, 33.94335995 ] } },
{ "type": "Feature", "properties": { "deviceID": 39399927, "type": 9, "channel": 0, "messageNumber": 172, "percentLEL": 41.000000, "temp": 30, "messageTime": "7\/23\/2015 3:00:26 PM", "sensorTime": "7\/23\/2015 3:01:20 PM", "lon": -118.326874, "lat": 33.943360 }, "geometry": { "type": "Point", "coordinates": [ -118.3268738, 33.94335995 ] } }
]
}