HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavascriptMinor

Plotting JSON locations using Google Road API with Javascript Promise chaining

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
locationsgooglewithplottingjavascriptchainingpromiseusingjsonroad

Problem

I wrote this code for an personal application, just to help me visualize some data related to driving. I have one JSON on my machine, this JSON contains a set of locations (lat, lngs) and every location has a category (integer number).

After I fetch this file I want to use Google's Road API, to snap the points on the road. Unfortunately it only allows 100 points per request. And depending on the "track" I can have usually 400+ points.

At this point, this application is only maintained by me. But I used this as an excuse to re-learn Javascript (including the new stuff only new browsers have).

I used Promises and I was used to the callback hell of before and after reading so much good stuff about Promises I got really frustrated seeing this huge chaining I made.

It's working, can't complain about that.
But, looks messy.

``
document.addEventListener('DOMContentLoaded', function(evt){
'use strict';

const apiKey = '-----------';

let rawMap = new google.maps.Map(document.getElementById('rawMap'), {
center: {lat: 0, lng: 0},
zoom: 10
});

let snappedMap = new google.maps.Map(document.getElementById('snappedMap'), {
center: {lat: 0, lng: 0},
zoom: 10
});

const colors = [
'#FF0000',
'#00FF00',
'#0000FF',
'#FFFF00',
'#00FFFF',
'#FF00FF',
'#000000',
];

fetch('output.json').then(response => response.json()).then(function(points){
for (let i = 0 ; i bounds.extend(point));
rawMap.fitBounds(bounds)
snappedMap.fitBounds(bounds);

let requests = [];

for (let i = 0; i
${point['lat']},${point['lng']}).join('|'));

requests.push(fetch(
https://roads.googleapis.com/v1/snapToRoads?${params}`));
}

return Promise.all(requests)
.then(responses => Promise.all(responses.map(response => response.json())))
.then(function(jsons){

Solution


  • Divide processing in simple functions, then chain them in linear fashion.



  • Use literal .property or ['property'] consistently, don't mix them as it's the same in JS



  • Concatenate arrays in one operation via Array.prototype.concat.apply([], arrayOfArrays) instead of reduce that creates intermediate arrays (it's slow and memory-hogging). However, if the number of elements may exceed 32k (the max number of arguments in some browsers) it's safer to concat in chunks of 10k by using a loop and .slice.



// your init part


let points;


const getJSON = (response) => response.json();


const drawPoints = (json) => {
    // will be used later for snapping to roads
    points = json;

    for (let i = 0; i  bounds.extend(point));
    rawMap.fitBounds(bounds);
    snappedMap.fitBounds(bounds);
};


const requestSnapToRoads = () => {
    let requests = [];
    for (let i = 0; i  `${point.lat},${point.lng}`)
            .join('|'));
        requests.push(fetch(`https://roads.googleapis.com/v1/snapToRoads?${params}`));
    }
    return Promise.all(requests).then(responses => Promise.all(responses.map(getJSON)));
};


const applySnapToRoads = (jsons) => {
    let lastIndex = -1;
    let allSnappedPoints = jsons.map((json, i) => {
        for (let snappedPoint of json.snappedPoints) {
            if ('originalIndex' in snappedPoint) {
                lastIndex = i * 100 + snappedPoint.originalIndex;
            }
            snappedPoint.cat = points[lastIndex].cat;
        }
        return json.snappedPoints;
    });
    return [].concat.apply([], allSnappedPoints);
};


const drawSnappedPoints = (snappedPoints) => {
    const getPointAt = (index) => ({
        lat: snappedPoints[index].location.latitude,
        lng: snappedPoints[index].location.longitude
    });
    for (let i = 0; i < snappedPoints.length - 1; i++) {
        let path = new google.maps.Polyline({
            path: [getPointAt(i), getPointAt(i + 1)],
            strokeWeight: 5,
            strokeColor: colors[snappedPoints[i].cat]
        });
        path.setMap(snappedMap);
    }
};



// chain them!

fetch('output.json')
    .then(getJSON)
    .then(drawPoints)
    .then(requestSnapToRoads)
    .then(applySnapToRoads)
    .then(drawSnappedPoints)
    .catch(console.log);

Code Snippets

// your init part
let points;
const getJSON = (response) => response.json();
const drawPoints = (json) => {
    // will be used later for snapping to roads
    points = json;

    for (let i = 0; i < points.length - 1; ++i) {
        let path = new google.maps.Polyline({
            path: [points[i], points[i + 1]],
            strokeColor: colors[points[i].cat],
            strokeWeight: 5
        });
        path.setMap(rawMap);
    }

    // fit map bounds to the points
    let bounds = new google.maps.LatLngBounds();
    points.forEach(point => bounds.extend(point));
    rawMap.fitBounds(bounds);
    snappedMap.fitBounds(bounds);
};
const requestSnapToRoads = () => {
    let requests = [];
    for (let i = 0; i < points.length; i += 100) {
        let params = new URLSearchParams();
        params.append('interpolate', 'true');
        params.append('key', apiKey)
        params.append('path', points
            .slice(i, i + 100)
            .map(point => `${point.lat},${point.lng}`)
            .join('|'));
        requests.push(fetch(`https://roads.googleapis.com/v1/snapToRoads?${params}`));
    }
    return Promise.all(requests).then(responses => Promise.all(responses.map(getJSON)));
};

Context

StackExchange Code Review Q#145616, answer score: 2

Revisions (0)

No revisions yet.