Skip to content

Instantly share code, notes, and snippets.

@jsanz
Last active January 2, 2019 16:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jsanz/78b26611aa4813c5a221fdd3ddabef37 to your computer and use it in GitHub Desktop.
Save jsanz/78b26611aa4813c5a221fdd3ddabef37 to your computer and use it in GitHub Desktop.
CARTO VL: Selection by polygon
<!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