-
-
Save makella/c74b2c2a81b6c7a2e1931ec4f793f66a to your computer and use it in GitHub Desktop.
CARTO VL: Selection by polygon
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<title>CARTO VL: Selection by polygon example</title> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=5.0"> | |
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | |
<link rel="shortcut icon" href="https://carto.com/favicon.ico"> | |
<link rel="stylesheet" href="https://libs.cartocdn.com/airship-style/v1.0.3/airship.css"> | |
<script src="https://libs.cartocdn.com/airship-components/v1.0.3/airship.js"></script> | |
<script src="https://libs.cartocdn.com/carto-vl/v1.0.0/carto-vl.min.js"></script> | |
<script src="https://api.tiles.mapbox.com/mapbox-gl-js/v0.50.0/mapbox-gl.js"></script> | |
<link href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.50.0/mapbox-gl.css" rel="stylesheet" /> | |
<script src="https://cdn.jsdelivr.net/npm/vue"></script> | |
<script src='https://npmcdn.com/@turf/turf/turf.min.js'></script> | |
<script src='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.0.9/mapbox-gl-draw.js'></script> | |
<link rel='stylesheet' href='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-draw/v1.0.9/mapbox-gl-draw.css' | |
type='text/css' /> | |
</head> | |
<body class="as-app-body as-app"> | |
<as-responsive-content> | |
<main class="as-main"> | |
<div class="as-map-area"> | |
<div id="map" style="width:100%;height:100vh;"></div> | |
</div> | |
</main> | |
<aside class="as-sidebar as-sidebar--right" data-name="Places"> | |
<div class="container"> | |
<section class="as-box" id="table"> | |
<h3 class="as-title">Places</h3> | |
<h2 class="as-subheader as-color--type-01" v-if="areSelection"> | |
Total population: {{ numberFormat(totalPopulation) }} | |
</h2> | |
<div> | |
<table v-if="areSelection" class="as-table as-table--stripped"> | |
<tr> | |
<th>Name</th> | |
<th>Population</th> | |
</tr> | |
<tr v-for="place in selected"> | |
<td>{{ place.properties.name }}</td> | |
<td style="text-align:right;">{{ numberFormat(place.properties.population) }} </td> | |
</tr> | |
</table> | |
<p class="as-color--primary" v-else> | |
Draw a polygon to select some populated places | |
</p> | |
</div> | |
</section> | |
</div> | |
</aside> | |
</as-responsive-content> | |
<script> | |
function run() { | |
const s = carto.expressions; | |
const map = new mapboxgl.Map({ | |
container: 'map', | |
style: carto.basemaps.voyager, | |
center: [101.6, 14.35], | |
zoom: 5, | |
scrollZoom: false, | |
hash: true | |
}); | |
map.addControl(new mapboxgl.NavigationControl({ showCompass: false }), 'top-left'); | |
carto.setDefaultAuth({ username: 'jsanzcdb', apiKey: 'default_public' }); | |
const base_query =`select cartodb_id, | |
cartodb_id::text as id, | |
the_geom_webmercator, | |
st_x(the_geom) as lon, | |
st_y(the_geom) as lat, | |
name, | |
pop_max | |
from populated_places`; | |
/* Base layer */ | |
const source_base = new carto.source.SQL(base_query); | |
const viz_base = new carto.Viz(` | |
\@list: viewportFeatures($name, $pop_max, $lon, $lat, $id) | |
color: opacity(red,0.2); | |
width: 10; | |
strokeColor: opacity(red,0.7); | |
strokeWidth: 1;`); | |
const layer = new carto.Layer('layer', source_base, viz_base); | |
layer.addTo(map, 'watername_ocean'); | |
/* Higlight layer */ | |
const source_highlight = new carto.source.SQL(base_query); | |
const viz_highlight = new carto.Viz(` | |
width: 10; | |
color: yellow; | |
strokeColor: gold; | |
strokeWidth: 2; | |
filter: 0; | |
@name: $id;`); | |
const layer_high = new carto.Layer('layer_high', source_base, viz_highlight); | |
layer_high.addTo(map, 'layer'); | |
// Vue app to control state and rendering | |
var app = new Vue({ | |
el: '#table', | |
data: { | |
numbFormatter: new Intl.NumberFormat('en-EN', { | |
minimumFractionDigits: 0, | |
maximumFractionDigits: 0 | |
}), | |
selected: null, | |
viz_base: viz_base, | |
viz_highlight: viz_highlight, | |
features: null | |
}, | |
watch: { | |
selected: function (new_selected) { | |
if (new_selected) { | |
// Set up a filter on the highlight layer by the names | |
const ids = new_selected.map(f => { return f.properties.id }); | |
this.viz_highlight.filter.blendTo(s.in(s.prop('id'), s.list(ids))); | |
// Fade the base layer | |
this.viz_base.color.blendTo('opacity(red,0.05);'); | |
this.viz_base.strokeColor.blendTo('opacity(red,0.1)'); | |
} else { | |
// Remove filter | |
this.viz_highlight.filter.blendTo(0); | |
// Reset the base layer | |
this.viz_base.color.blendTo('opacity(red,0.2);'); | |
this.viz_base.strokeColor.blendTo('opacity(red,0.7)'); | |
} | |
} | |
}, | |
computed: { | |
areSelection: function () { return this.selected && this.selected.length > 0 }, | |
totalPopulation: function() { return this.selected.reduce((acc, curr) => acc + curr.properties.population, 0) } | |
}, | |
methods: { | |
numberFormat: function (number) { return this.numbFormatter.format(number); }, | |
updateArea: function (e) { | |
const type = e.type; | |
// Remove the selection when selection is also removed | |
if ( (type == 'draw.delete' || type == 'draw.update') && this.selected) { | |
this.selected = null; | |
} | |
// If a polygon is drawn, filter viewport features | |
if (type == 'draw.create' || type == 'draw.update') { | |
const polygon = e.features.length > 0 ? e.features[0] : null; | |
if (polygon) { | |
this.selected = this.features.filter(feature => { | |
return turf.booleanContains(polygon, feature) | |
}).sort(function (a, b) { | |
// Sort by population desc. | |
return b.properties.population - a.properties.population | |
}) | |
} | |
} | |
} | |
} | |
}); | |
// When viewport is updated, set up the features | |
layer.on('updated', () => { | |
app.features = viz_base.variables.list.value.map( feature => { | |
const props = feature.properties; | |
return turf.point( | |
[props.lon, props.lat], // geometry | |
{ 'id': props.id, 'name': props.name, 'population': parseInt(props.pop_max) } // properties | |
); | |
}); | |
}); | |
/* Draw widget */ | |
var draw = new MapboxDraw({ | |
displayControlsDefault: false, | |
controls: { polygon: true, trash: true } | |
}); | |
map.addControl(draw); | |
map.on('draw.create', app.updateArea); | |
map.on('draw.update', app.updateArea); | |
map.on('draw.delete', app.updateArea); | |
} | |
// Run the script when content is ready | |
document.querySelector('as-responsive-content').addEventListener('ready', run); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment