Home > Net >  Break large array into 10 batches (regardless of batch size). TypeScript/Javascript
Break large array into 10 batches (regardless of batch size). TypeScript/Javascript

Time:10-05

I have a logic problem I am stuck on and could use some help. I think this would be normally easy for me, but I am a little brain fried after hurricane Ian here in FL.

I am plotting a gradient line on an Azure map. My GPS service returns a large array of coordinates (latitude/longitude) and related data for those points along the route.

I am finding that for longer routes, Azure maps will only accept up to 10 coordinates for the lineLayer method.

My issue is more with logic. I have an array of coordinates with maybe 4000 items. Sometimes more, sometimes less depending on the length of the chosen route.

I need to break that 4000 items into 10 chunks, from there, I will take the first item in each of the 10 and use that to plot my data point.

So for 4000 items, my chunked array size would be 400. 2000 items would be 200, 100, would be 10, 50 would be 5, etc. If there is overflow in the last chunk, that is fine.

//This is the top temperature
this.finalTemp = 75

//I need to break points into 10 chunks so the Azure maps lineLayer expression can handle it.
for (var i = 0; i < points.length; i  ) {
  let temperatureAtPoint = points[i].temperature //22.0 - this will usually increase as we iterate through.
  let progressValue: number = pointTemp / this.finalTemp;

  //this progressValue will be used to generate a color that is plotted on the map (I have that working)

}

For the life of me, I can't get this figured out and thought I would hop on here to see if someone can get me straight. I just need a typescript or Javascript example.

Thank you!

CodePudding user response:

Azure Maps line layer has no limit on the number of coordinates in the line. That said, if you try and create a gradient based on thousands of stops/colors, then that would likely be slow.

A few of approaches to show your data as a gradient:

  1. Show the individual points using the bubble layer and color each bubble based the temperature in that point. As a bonus, if you have additional metadata in your point, you can easily retrieve that and show it in a popup when the user interacts with it. Here is an example:

enter image description here

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

    <style>
        body, html {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
        }

        #myMap {
            width: 100vw;
            height: 100vh;
        }
    </style>
</head>
<body onl oad="GetMap()">
    <div id="myMap"></div>

    <!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
    <link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" rel="stylesheet" />
    <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js"></script>

    <script>
        var map, datasource;

        //From: https://samples.azuremaps.com/data/geojson/GpsTrace.json
        var gpsTrace = 'GpsTrace.json';

        //The property name to base the gradient on.
        var metric = 'ele';


        function GetMap() {
            map = new atlas.Map('myMap', {
                center: [12.858333, 43.247383],
                zoom: 12,
                style: 'grayscale_dark',
                authOptions: {
                    authType: 'subscriptionKey',
                    subscriptionKey: '<Your Azure Maps Key>'
                }
            });

            map.events.add('ready', function () {

                datasource = new atlas.source.DataSource();
                map.sources.add(datasource);

                datasource.importDataFromUrl(gpsTrace);

                map.layers.add(new atlas.layer.BubbleLayer(datasource, null, {
                    strokeWidth: 0,
                    color: [
                        'interpolate',
                        ['linear'],
                        ['get', metric],
                        300, "blue",
                        400, "royalblue",
                        500, "cyan",
                        600, "lime",
                        700, "yellow",
                        800, "red"
                    ]
                }));
            });
        }
    </script>
</body>
</html>
  1. Break your lines up into line segments (2-point lines) and capture the temperature as a property of each segment, then use a data driven style expression to color the line segments.
  2. Similar to #2 but calculate the mid-points between points since that is more representative of when the metric is valid.
  3. Similar to #2 and 3 but combine the points where the temperature is the same. This will be more optimized as the render will be able to reduce the resolution of these multi-point lines when zoomed out. Here is an example:

enter image description here

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

    <style>
        body, html {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
        }

        #myMap {
            width: 100vw;
            height: 100vh;
        }
    </style>
</head>
<body onl oad="GetMap()">
    <div id="myMap"></div>

    <!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
    <link href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" rel="stylesheet" />
    <script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.js"></script>

    <script>
        var map, datasource;

        //From: https://samples.azuremaps.com/data/geojson/GpsTrace.json
        var gpsTrace = 'GpsTrace.json';

        //The property name to base the gradient on.
        var metric = 'ele';


        function GetMap() {
            map = new atlas.Map('myMap', {
                center: [12.858333, 43.247383],
                zoom: 12,
                style: 'grayscale_dark',
                authOptions: {
                    authType: 'subscriptionKey',
                    subscriptionKey: '<Your Azure Maps Key>'
                }
            });

            map.events.add('ready', function () {

                datasource = new atlas.source.DataSource();
                map.sources.add(datasource);

                datasource.importDataFromUrl(gpsTrace).then(chunkPoints);

                map.layers.add(new atlas.layer.LineLayer(datasource, null, {
                    strokeWidth: 6,
                    strokeColor: [
                        'interpolate',
                        ['linear'],
                        ['get', metric],
                        300, "blue",
                        400, "royalblue",
                        500, "cyan",
                        600, "lime",
                        700, "yellow",
                        800, "red"
                    ]
                }));
            });
        }

        function chunkPoints() {
            var points = datasource.toJson().features;

            //Convery GPS points into line segments.
            var lines = [];

            var currentMetric = null;
            var coords = null;

            for (var i = 0; i < points.length; i  ) {
                var p = points[i];

                //If the metric value has changed, need to create a line with current data and collect data for the next line.
                if (p.properties[metric] !== currentMetric) {
                    var midPoint;

                    //If there is no existing coords, must be starting out.
                    if (coords !== null) {
                        //Calculate the mid-point between current and last point.
                        midPoint = atlas.math.interpolate(coords[coords.length - 1], p, 0.5);

                        coords.push(midPoint);

                        //Create a line for the current metric and coords.
                        var props = {};
                        props[metric] = currentMetric;
                        lines.push(new atlas.data.Feature(new atlas.data.LineString(coords), props));
                    }

                    currentMetric = p.properties[metric];
                    coords = [];

                    //Start the new line 
                    if (midPoint) {
                        coords.push(midPoint);
                    }
                }

                //Capture the point coordinates.
                coords.push(p.geometry.coordinates);
            }

            //Add the remaining current line.
            if (currentMetric != null && coords !== null) {
                var props = {};
                props[metric] = currentMetric;
                lines.push(new atlas.data.Feature(new atlas.data.LineString(coords), props));
            }

            //Add the lines to the data source.
            datasource.setShapes(lines);
        }

    </script>
</body>
</html>

If you still find that your data is too big, you then may need to consider reducing the resolution of your data, or serving it in a more efficient format, such as vector tiles.

CodePudding user response:

The following should suffice

function sliceIntoChunks(arr, chunkSize) {
    const res = [];
    for (let i = 0; i < arr.length; i  = chunkSize) {
        const chunk = arr.slice(i, i   chunkSize);
        res.push(chunk);
    }
    return res;
}

// the remainder is important because if not divisible then it would be a float number, we can safely omit it
let chunkSize = (points.length   (points.length % 10)) / 10
const chunkedArray = sliceIntoChunks(points, chunkSize)

This article sums it up pretty well.

  • Related