drewfustin / isocronut

For a given geospatial location, calculate an isochrone (same time) contour around it.
97 stars 44 forks source link

Javascript implementation issues #7

Open wolframteetz opened 8 years ago

wolframteetz commented 8 years ago

If the complexity of the requested isochrones is high, Chrome sais the script crashes and Firefox repeatedly throws unresponsive script errors. Would be great if we could fix this.

Example

    var m = [20, 50, 100, 300, 400, 500, 600, 700, 800, 900, 1000];
    var s = [20, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100];

(Complete "time-out-problematic" html without token, at least on my mac it times out)

 <!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>Isochrone</title>

<script src="https://api.tiles.mapbox.com/mapbox.js/v2.2.2/mapbox.js"></script>
<script src="https://api.tiles.mapbox.com/mapbox.js/plugins/turf/v2.0.0/turf.min.js" charset="utf-8"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

<link href='https://api.tiles.mapbox.com/mapbox.js/v2.2.2/mapbox.css' rel='stylesheet' />
<style>
  body { margin:0; padding:0; }
  #map { position:absolute; top:0; bottom:0; width:100%; }
</style>

</head>
<body>

<div id="map"></div>

<script>
var accessToken = '  $$$ TOKEN $$$ ';

L.mapbox.accessToken = accessToken;

var map = L.mapbox.map('map', 'mapbox.streets')
    .setView([48.10892,11.451958], 15);

map.on('click', function(e) {
    create_isochrones(map, [e['latlng']['lat'], e['latlng']['lng']]);
});

function create_isochrones(map, origin) {

    map.setView(origin, 6); // Zoom out 

    var well = {
        type: 'Feature',
        geometry: {
            type: 'Point',
            coordinates: [
              origin[0],
              origin[1]
            ]
        },
        properties: {}
    };

    L.mapbox.featureLayer(turf.flip(well)).addTo(map);

    var m = [20, 50, 100, 300, 400, 500, 600, 700, 800, 900, 1000];
    var s = [20, 50, 100, 100, 100, 100, 100, 100, 100, 100, 100];

    for (var i in m) {
        if (i > 0) {
            var circle = turf.buffer(well, m[i], 'miles');
            var extent = turf.extent(circle);
            var additions = turf.random('points', s[i], {bbox: extent});
            points['features'].push.apply(points['features'], additions['features']);
        } else {
            var circle = turf.buffer(well, m[i], 'miles');
            var extent = turf.extent(circle);
            var points = turf.random('points', s[i], {bbox: extent});
        };
    };

    var deferreds = [];
    var destinations = {
        type: 'FeatureCollection',
        features: []
    };

    for (i = 0; i < points.features.length; i++) {
        var url = 'https://api.tiles.mapbox.com/v4/directions/mapbox.driving/' + well.geometry.coordinates[1].toString() + ',' + well.geometry.coordinates[0].toString() + ';' + points.features[i].geometry.coordinates[1].toString() + ',' + points.features[i].geometry.coordinates[0].toString() + '.json?access_token=' + L.mapbox.accessToken;
        deferreds.push(
            $.ajax({
              url: url,
              contentType: 'text/plain',
              type: 'GET',
              xhrFields: {
                withCredentials: false
              },
            })
            .success(function(data) {
                var min_dur = 9999999;
                for (r = 0; r < data['routes'].length; r++) {
                    min_dur = Math.min(min_dur, data['routes'][r]['duration'] / 60);
                };
                if (data['destination']) {
                    var d = {
                        type: 'Feature',
                        geometry: {
                            type: 'Point',
                            coordinates: [
                                data['destination']['geometry']['coordinates'][1],
                                data['destination']['geometry']['coordinates'][0]
                            ]
                        },
                        properties: {
                            'z': min_dur
                        }
                    };
                destinations['features'].push(d);
                };
            })
            .fail(function() {
              alert("Ajax failed to fetch data")
            })
        );
    }

    $.when.apply($, deferreds).done(
        function() {
            var breaks = [10, 20, 30, 60, 90, 120, 180, 240, 300, 420, 540, 660];
            var colors = {};
            colors[breaks[0]] = 'rgb(255, 255, 255)';
            colors[breaks[1]] = 'rgb(255, 255, 191)';
            colors[breaks[2]] = 'rgb(255, 191, 191)';
            colors[breaks[3]] = 'rgb(255, 191, 127)';
            colors[breaks[4]] = 'rgb(255, 127, 127)';
            colors[breaks[5]] = 'rgb(255, 127, 63)';
            colors[breaks[6]] = 'rgb(255, 63, 63';
            colors[breaks[7]] = 'rgb(255, 63, 0)';
            colors[breaks[8]] = 'rgb(255, 0, 0)';
            colors[breaks[9]] = 'rgb(191, 0, 0)';
            colors[breaks[10]] = 'rgb(127, 0, 0)';
            colors[breaks[11]] = 'rgb(63, 0, 0)';
            colors[breaks[12]] = 'rgb(0, 0, 0)';
            var isolined = turf.isolines(destinations, 'z', 500, breaks);

            for (i = isolined.features.length - 1; i >= 0; i--) {
                var polygon_options = {
                    color: '#000',
                    opacity: 0.5,
                    weight: 1,
//                      fillColor: colors[isolined.features[i].properties.z],
                    fillColor: 'rgb(0, 0, 0)',
                    fillOpacity: 0.1
                };
                L.polygon(isolined.features[i].geometry.coordinates, polygon_options).addTo(map);
            }
        }
    );

};

</script>

</body>
</html>
wolframteetz commented 8 years ago

There are also some issues on the image generated

a) There is one weird line "across europe". b) Close to venice, the travel time decreases when travelling out into the ocean. This is very weird, don't know where that comes from... Probably mapbox knows I own a small private airplane and if going to the adriatic sea over the weekend, I won't go by car. Clever, mapbox, clever.

wolframteetz commented 8 years ago

Apart from these minor issues, this is GREAT STUFF ;) I love it

drewfustin commented 8 years ago

a) Ah yes. I spent an entire day trying to figure this nonsense out. The problem: if the points you randomly pick are within too small of a region to fully contain the isochrone contour, the isoline will go "off the screen," so to speak. So instead of being a closed loop, these contours will be something like 2 separated lines. Connecting those points then gets confusing, which is why the contour jumps across the map. The solution: make the bounding boxes from which random points are drawn larger. b) Water, man. I have no idea what MapBox is doing when you throw them a point out in the sea. I just hope it handles it intelligently. Guess not...

Anyhow, in summary, it's probably best to describe exactly what:

        var m = [50, 150, 300];
        var s = [50, 100, 100];

is doing. That means, for a box of size (m[0] = ) 50 miles on each side (I think), draw (s[0] = ) 50 points. For a box of size (m[1] = ) 150 miles, draw (s[1] = ) 100 points. For a box of size (m[2] = ) 300 miles, draw (s[2] = ) 100 points. I do it this way so regions near the origin are well-explored with higher density points. You can't just add a billion points because the complexity crashes it (which I think is what you're seeing). So, to get the most accurate contours, I tried this method.

The fun part about this is that the way I have it done is sort of stupid/hacky. There's an actual CORRECT choice for the test point density distribution to make this optimal. I just haven't put the time into figuring out what it is. Presumably, it's something that goes as 1 / r^2.

vidiot1969 commented 6 years ago

I too experience these browser errors as described by Wolf, "Ajax failed to fetch data". Was there any solution? It does not not seem to matter how closely zoomed in I am. Thanks!