Skip to content

Instantly share code, notes, and snippets.

@andrewharvey
Created November 12, 2019 10:27
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 andrewharvey/bd50f324bc90617c45ec797b46e4e352 to your computer and use it in GitHub Desktop.
Save andrewharvey/bd50f324bc90617c45ec797b46e4e352 to your computer and use it in GitHub Desktop.
Mapbox GL JS Workshop Custom Layer
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<script src='https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.js'></script>
<link href='https://api.tiles.mapbox.com/mapbox-gl-js/v1.5.0/mapbox-gl.css' rel='stylesheet' />
<script src='https://unpkg.com/three@0.106.2/build/three.min.js'></script>
<script src="https://unpkg.com/three@0.106.2/examples/js/loaders/GLTFLoader.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id='map'></div>
<script>
mapboxgl.accessToken = 'pk.eyJ1IjoiYWxhbnRnZW8tcHJlc2FsZXMiLCJhIjoiY2pxcmZ1cW1mMG1tcDN4bDVvYzA4MWg5MyJ9.7QtVj_0ythHwEg1n_zaRTQ';
var map = new mapboxgl.Map({
container: 'map',
style: {
version: 8,
sources: {
countries: {
type: 'geojson',
data: 'https://www.alantgeo.com.au/share/countries.geojson',
generateId: true
},
cities: {
type: 'geojson',
data: 'https://www.alantgeo.com.au/share/cities.geojson'
}
},
glyphs: 'mapbox://fonts/mapbox/{fontstack}/{range}.pbf',
layers: [
{
id: 'background',
type: 'background',
paint: {
'background-color': 'lightblue'
}
},
{
id: 'country-fill',
type: 'fill',
source: 'countries',
paint: {
'fill-color': [
'case',
['boolean', ['feature-state', 'hover'], false],
'#c5b5a9',
'#ffebda'
]
}
},
{
id: 'country-line',
type: 'line',
source: 'countries',
paint: {
'line-width': 0.5
}
},
{
id: 'city-dot',
type: 'circle',
source: 'cities',
paint: {
'circle-color': 'white',
'circle-radius': 3,
'circle-stroke-width': 1,
'circle-stroke-color': 'black'
}
},
{
id: 'city-label',
type: 'symbol',
source: 'cities',
paint: {
'text-halo-color': 'white',
'text-halo-width': 2
},
layout: {
'text-field': ['get', 'name'],
'text-variable-anchor': ['left', 'right', 'top-left', 'bottom-left', 'top-right', 'bottom-right'],
'text-justify': 'auto',
'text-radial-offset': 0.2,
'text-size': [
'interpolate', ['linear'],
['zoom'],
// zoom 4 or less -> size is 10
2, 10,
// zoom 6 or more -> size is 14
3, 14
]
}
}
]
},
antialias: true
})
var navigation = new mapboxgl.NavigationControl({
visualizePitch: true
});
map.addControl(navigation, 'top-right');
var geolocate = new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true
});
map.addControl(geolocate, 'top-right');
var fullscreen = new mapboxgl.FullscreenControl();
map.addControl(fullscreen, 'top-right');
var scale = new mapboxgl.ScaleControl({
unit: 'metric'
});
map.addControl(scale, 'bottom-right');
var marker = new mapboxgl.Marker({
color: 'darkred',
draggable: true
})
.setLngLat([175, -45])
.addTo(map);
var hoveredCountry;
map.on('mousemove', 'country-fill', function (e) {
map.getCanvas().style.cursor = 'pointer';
if (e.features.length) {
if (hoveredCountry) {
map.setFeatureState({
source: 'countries',
id: hoveredCountry
}, {
hover: false
});
}
hoveredCountry = e.features[0].id;
map.setFeatureState({
source: 'countries',
id: hoveredCountry
}, {
hover: true
});
}
});
map.on('mouseleave', 'country-fill', function (e) {
map.getCanvas().style.cursor = '';
if (hoveredCountry) {
map.setFeatureState({
source: 'countries',
id: hoveredCountry
}, {
hover: false
});
hoveredCountry = null;
}
});
map.on('click', 'country-fill', function (e) {
var popup = new mapboxgl.Popup()
.setText(e.features[0].properties.ADMIN)
.setLngLat(e.lngLat)
.addTo(map)
map.fitBounds(e.features[0].properties.bbox.split(','), {
padding: 20
});
});
// parameters to ensure the model is georeferenced correctly on the map
var modelOrigin = [175.880, -38.801];
var modelAltitude = 0;
var modelRotate = [Math.PI / 2, 0, 0];
var modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(modelOrigin, modelAltitude);
// transformation parameters to position, rotate and scale the 3D model onto the map
var modelTransform = {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: modelRotate[0],
rotateY: modelRotate[1],
rotateZ: modelRotate[2],
/* Since our 3D model is in real world meters, a scale transform needs to be
* applied since the CustomLayerInterface expects units in MercatorCoordinates.
*/
scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits() * 1000000
};
var THREE = window.THREE;
var customLayer = {
id: 'pokemon',
type: 'custom',
renderingMode: '3d',
onAdd: function(map, gl) {
this.camera = new THREE.Camera();
this.scene = new THREE.Scene();
// create two three.js lights to illuminate the model
var directionalLight = new THREE.DirectionalLight(0xffffff);
directionalLight.position.set(0, -70, 100).normalize();
this.scene.add(directionalLight);
var directionalLight2 = new THREE.DirectionalLight(0xffffff);
directionalLight2.position.set(0, 70, 100).normalize();
this.scene.add(directionalLight2);
// use the three.js GLTF loader to add the 3D model to the three.js scene
var loader = new THREE.GLTFLoader();
loader.load('https://www.alantgeo.com.au/share/bulbasaur.gltf', (function (gltf) {
this.scene.add(gltf.scene);
}).bind(this));
this.map = map;
// use the Mapbox GL JS map canvas for three.js
this.renderer = new THREE.WebGLRenderer({
canvas: map.getCanvas(),
context: gl,
antialias: true
});
this.renderer.autoClear = false;
},
render: function(gl, matrix) {
var rotationX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), modelTransform.rotateX);
var rotationY = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), modelTransform.rotateY);
var rotationZ = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), modelTransform.rotateZ);
var m = new THREE.Matrix4().fromArray(matrix);
var l = new THREE.Matrix4().makeTranslation(modelTransform.translateX, modelTransform.translateY, modelTransform.translateZ)
.scale(new THREE.Vector3(modelTransform.scale, -modelTransform.scale, modelTransform.scale))
.multiply(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
this.camera.projectionMatrix = m.multiply(l);
this.renderer.state.reset();
this.renderer.render(this.scene, this.camera);
this.map.triggerRepaint();
}
};
map.on('style.load', function() {
map.addLayer(customLayer);
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment