├── .gitignore ├── README.md ├── assets ├── cat-shaq.gif └── hydrants.geojson ├── bundle.js ├── index.html ├── main.js ├── package.json └── worker.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /.tern-port 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Turf Async 2 | 3 | A simple Mapbox-GL.js app demonstrating how to use Turf.js with web workers to run geoprocesses asynchronously 4 | 5 | ## Why? 6 | Turf.js is great for doing small geoprocessing jobs in the browser. However, with larger jobs, it can freeze the browser for long periods. While the browser is waiting for the Turf process to complete, you can't pan or zoom on the map or even switch browser tabs. 7 | 8 | With web workers we can move the geoprocessing job off the main user interface thread. This means we can still manipulate the map and switch browser tabs while we wait for the job to complete. 9 | 10 | [Demo](https://nickpeihl.github.io/turf-async) 11 | 12 | Notice how the dance party stops momentarily when we click the Turf Sync button? Don't let the dance party stop! Click the Turf Async button to run the process in a web worker. This frees up the user interface resources for the dance party to continue! 13 | -------------------------------------------------------------------------------- /assets/cat-shaq.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nickpeihl/turf-async/0e4199b3542fe9090af89e83c7d62e7fe284b3b0/assets/cat-shaq.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | 64 | 65 | 66 |
67 |
68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | var work = require('webworkify'); 2 | var mapboxgl = require('mapbox-gl'); 3 | var concave = require('@turf/concave'); 4 | var geojsonhint = require('geojsonhint'); 5 | var geobuf = require('geobuf'); 6 | var Pbf = require('pbf'); 7 | 8 | var w = work(require('./worker.js')); 9 | w.addEventListener('message', function(ev) { 10 | var gj = geobuf.decode(new Pbf(ev.data[1])); 11 | map.getSource('results').setData(gj); 12 | 13 | }); 14 | w.addEventListener('error', function(err) { 15 | console.log(err); 16 | }); 17 | 18 | mapboxgl.accessToken = 'pk.eyJ1IjoibnBlaWhsIiwiYSI6InVmU21qeVUifQ.jwa9V6XsmccKsEHKh5QfmQ'; 19 | 20 | var map = new mapboxgl.Map({ 21 | container: 'map', 22 | style: 'mapbox://styles/mapbox/streets-v8', 23 | center: [-122.963104,48.569792], 24 | zoom: 10 25 | }); 26 | 27 | var emptyGeoJson = { 28 | type: 'FeatureCollection', 29 | features: [] 30 | }; 31 | 32 | var dataUrl = 'assets/hydrants.geojson'; 33 | 34 | map.on('style.load', function() { 35 | console.log('style loaded'); 36 | map.addSource('points', { 37 | type: 'geojson', 38 | data: dataUrl 39 | }); 40 | 41 | map.addSource('results', { 42 | type: 'geojson', 43 | data: emptyGeoJson 44 | }); 45 | 46 | map.addLayer({ 47 | 'id': 'points', 48 | 'type': 'circle', 49 | 'source': 'points' 50 | }); 51 | 52 | map.addLayer({ 53 | 'id': 'results', 54 | 'type': 'fill', 55 | 'source': 'results', 56 | 'paint': { 57 | 'fill-opacity': 0.6, 58 | 'fill-color': '#00FF00' 59 | } 60 | }); 61 | }); 62 | 63 | map.on('source.load', function() { 64 | console.log('source loaded'); 65 | }); 66 | 67 | addButton('Turf Sync', 'turf-sync'); 68 | addButton('Turf Async','turf-async'); 69 | 70 | function addButton(name, id) { 71 | var link = document.createElement('a'); 72 | link.href = '#'; 73 | link.id = id; 74 | link.className = 'active'; 75 | link.textContent = name; 76 | 77 | link.onclick = function(e) { 78 | e.preventDefault(); 79 | e.stopPropagation(); 80 | 81 | switch (id) { 82 | case 'turf-sync': 83 | turfSync(); 84 | break; 85 | case 'turf-async': 86 | turfAsync(); 87 | break; 88 | } 89 | }; 90 | var menu = document.getElementById('menu'); 91 | menu.appendChild(link); 92 | } 93 | 94 | 95 | function turfSync() { 96 | map.getSource('results').setData(emptyGeoJson); 97 | var absUrl = window.location + dataUrl; 98 | makeRequest(dataUrl); 99 | } 100 | 101 | function makeRequest(url) { 102 | var req = new XMLHttpRequest(); 103 | req.addEventListener('load', validateData); 104 | req.addEventListener('error', transferFailed); 105 | req.open('GET', url); 106 | req.send(); 107 | } 108 | 109 | function validateData() { 110 | var errors = geojsonhint.hint(this.responseText); 111 | if (errors.len > 0) { 112 | console.log('Errors', errors.join(', ')); 113 | } else { 114 | turfIt(this.responseText); 115 | } 116 | } 117 | 118 | function transferFailed() { 119 | console.log('Error', this.responseText); 120 | } 121 | 122 | function turfIt(data) { 123 | var results = concave(JSON.parse(data), 0.75, 'miles'); 124 | map.getSource('results').setData(results); 125 | } 126 | 127 | function turfAsync() { 128 | map.getSource('results').setData(emptyGeoJson); 129 | var absUrl = window.location + dataUrl; 130 | w.postMessage([absUrl]); 131 | } 132 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "turf-async", 3 | "version": "1.0.0", 4 | "description": "Demonstrating how to use Turf.js with web workers to run geoprocesses asynchronously", 5 | "main": "main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "budo main.js --serve bundle.js", 9 | "build": "browserify main.js > bundle.js" 10 | }, 11 | "author": "Nick Peihl ", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@turf/concave": "^3.5.2", 15 | "geobuf": "^3.0.0", 16 | "geojsonhint": "^2.0.0", 17 | "mapbox-gl": "^0.31.0", 18 | "pbf": "^3.0.2", 19 | "webworkify": "^1.4.0" 20 | }, 21 | "devDependencies": { 22 | "browserify": "^13.1.0", 23 | "budo": "^9.2.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | var concave = require('@turf/concave'); 2 | var geojsonhint = require('geojsonhint'); 3 | var geobuf = require('geobuf'); 4 | var Pbf = require('pbf'); 5 | 6 | module.exports = function(self) { 7 | self.addEventListener('message', function(ev) { 8 | var dataUrl = ev.data[0]; 9 | self.makeRequest(dataUrl); 10 | }); 11 | 12 | self.makeRequest = function(url) { 13 | var req = new XMLHttpRequest(); 14 | req.addEventListener('load', self.validateData); 15 | req.addEventListener('error', self.transferFailed); 16 | req.open('GET', url); 17 | req.send(); 18 | }; 19 | 20 | self.validateData = function() { 21 | var errors = geojsonhint.hint(this.responseText); 22 | if (errors.len > 0) { 23 | self.postMessage(['Errors', errors.join(', ') ]); 24 | } else { 25 | self.turfIt(this.responseText); 26 | } 27 | }; 28 | 29 | self.transferFailed = function() { 30 | self.postMessage(['Error', this.responseText]); 31 | }; 32 | 33 | self.turfIt = function(data) { 34 | var results = concave(JSON.parse(data), 0.75, 'miles'); 35 | var buf = geobuf.encode(results, new Pbf()); 36 | self.postMessage(['Done', buf]); 37 | }; 38 | }; 39 | --------------------------------------------------------------------------------