infusion-code / angular-maps

Angular Maps (X-Map) is a set of components and services to provide map functionality in angular 2+ apps. X-Maps architecture is provider independent and can be used with Bing, Google, ESRI or any other service enabled mapping provider. X-Map contains a default implementation for Bing Maps.
MIT License
41 stars 34 forks source link

Bing Maps Polyline and Map Marker #48

Closed drewteachout closed 6 years ago

drewteachout commented 6 years ago

When using map-markers and polylines with bing maps, if I have a dataset that is a decent size (200+ items in the list) the map takes a very long time to load the polylines and map-markers. Is there anyway to improve this speed?

thor-schueler commented 6 years ago

@drewteachout, 200 markers should not be a problem at all, but look at using a marker layer and a polyline layer as opposed to individual polylines and makers. Your performance problem probably comes with the polylines. Depending on the number of points in each line, you might see slowdowns with relatively few lines.

We have successfully used data reduction algorithms depending on the resolution such as https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm to optimize the number of points per line depending on the resolution.

Also, check out the examples page, it has some examples with large number of markers using the marker layer. Polyline layer works in the same way….

@MetaCEO, can you elaborate a bit on the data reduction algorithm approach?

@drewteachout, let us know how this goes…

drewteachout commented 6 years ago

@thor-schueler, I am using lines with 32 points. In your experience is this two large a number? I totally glanced over polyline layer and will look into this. I'll post an update with how it goes.

thor-schueler commented 6 years ago

32 points is not really that much. If you have 200 lines with 32 points each, you are drawing essentially 6400 segments. That should not take more than a few hundred ms... What times are you seeing? Can you share the data structure you are using or setup a quick test on plunker for me to look at it?

The polyline layer will definitely improve the rendering speed as it reduces the bloat generated when expanding the polylines via an ngFor. However, with 200 lines that should not really be that big of a deal.

METACEO commented 6 years ago

While most of my integration experience is with Google Maps, I don't see 32-point lines being difficult for Bing maps to render either. @drewteachout could you provide a sample of your PolylineOptions?.. I'd like to compare them to what I'm using a lot of.

Regarding data reduction, we're implementing simplify-ts with something like the below. This can reduce 1,000s of points to 100s or less while still keeping a reasonable shape. As @thor-schueler suggested, it's using the RDP algorithm underneath with some TypeScript exports.

import {map} from 'lodash';
import {SimplifyLL} from 'simplify-ts';

...

function getTolerance(resolution) {
    // If zoomed in, return null for full resolution.
    if (resolution >= 12) {
        return null;
    }
    // The smaller the returned value, the higher the resolution.
    if (resolution >= 10) {
        return 0.001;
    }
    if (resolution >= 8) {
        return 0.025;
    }
    return 0.05;
}

function getPolylinePaths(resolution, polyline_paths) {
    // Get the tolerance to use according to the provided angular-maps resolution:
    const tolerance = getTolerance(resolution);
    // Only if we get a valid number back do we perform simplification:
    if (tolerance) {
        return map(polyine_paths, path => SimplifyLL(path, tolerance, false));
    }
    // Otherwise return back the original polylines:
    return polyline_paths;
}
drewteachout commented 6 years ago

So my data is a bunch of LineInformation objects (a model of my own creation that is a bunch of data about the line and the start and end lat/longs for the line I want to draw).

When parsing the .json data from the api call I make a Map of these LineInformation objects with keys being unique identifiers for different start/end pairs. This was in an effort to not try and draw the exact same line twice even though my data repeats it.

Below is the code that loops through the key values and draws lines from the LineInformation objects. Points is an array of size 33 that has the points of the line. Calculated when the LineInformation object is created.

const p: Array<IPolylineOptions> = new Array<IPolylineOptions>();
for (let key of this.keyArray) {
    p.push({
        id: 0,
        path: this.linkMap.get(key)[0].points,
        strokeWeight: 5,
        strokeColor: this.masterColor
    });
}
this.polylineArray = p;

this.polylineArray is what I pass in as the value of [PolylineOptions} in the html code shown here.

<div class="map">
  <x-map #xmap [Options]="options">
    <x-map-marker *ngFor="let loc of locationArray"
      [Latitude]="loc.latitudeNumber"
      [Longitude]="loc.longitudeNumber"
      [IconInfo]="iconInfo">
      <x-info-box
        [Title]="My Data"
        [Description]="My description">
      </x-info-box>
    </x-map-marker>
    <x-map-polyline-layer
      [PolylineOptions]="polylineArray"
    ></x-map-polyline-layer>
  </x-map>
</div>

Overall, it takes 5000ms or more each time to load the map. This is comparable to the times I saw when using x-map-polyline and an *ngFor. Is it because of my use of infoboxes on every location that causes the rendering to take longer?

thor-schueler commented 6 years ago

Actually, depending on how many markers you have I suspect the problem might be the icon info. Depending on the type of icon you provide, the rendition might take quite long, so if you have a few hundred markers, that could explain your slowdown.

Are the marker icons different for each marker? If not, please try using the id property in the IMarkerInfo and use the same id for markers that use the same icon. We cache data urls based on id if it is present. That way, there is not a new data url created for each marker (which is quite time consuming, depending on the transformation).

The other thing that would be interesting would be to see what dropping the markers from your map does to performance. That should isolate whether the issue is with the markers or with the polylines…

As for the Infobox: generally speaking, if you get to more than a few hundred markers, it would be better to move the infobox outside the markers and only have one infobox that you position on click over the marker using the LatLong that is being passed by the marker click event. While there is not too much overhead with an infobox, it does add up as the numbers grow. I don’t think that is your problem here though (but again, you can drop the infobox temporarily to see how that impacts your performance).

drewteachout commented 6 years ago

I believe you are right about the marker thing. When just using polylines it took about a second or less to render all the polylines. Currently I am using one image as the marker, but will need to use somewhere between 5 and 10 in the real application. Is there an example of how the marker id works so I can try it out in my application?

As far as the infobox goes each one will have unique information. Are you saying there is a way I can display unique infoboxes on each marker, but with only one infobox that moves locations based on where I click?

Additionally, would using an x-map-marker-layer be helpful instead of an *ngFor?

thor-schueler commented 6 years ago

Re the marker images: If currently they all use the same, simply add this in the iconInfo:

iconInfo = { id: 'myMarkerIcon', markerType: …, … }

As you move to more than one icon, simply use a different id for each distinct icon you want to use. Take a look at http://plnkr.co/edit/bb8wu5?p=info. Also, the different marker types have different performance characteristics. Canvas and Font-based will generally perform better than the others.

Re the infoboxes: yes, correct. That is the preferred way when you have a more than a few hundred of markers. The marker click event gives you (amongst others) the marker model. That in turn can carry arbitrary information in the Metadata map, which is also an input on the component. You can bind the infobox lat, long, title, description to variables and simply update those in the click event based on the marker that you get…

This is also the way you would use to build a custom infobox for which you want to perhaps do a more compelling and interactive display than you can do with the OOB infoboxes.

drewteachout commented 6 years ago

Where can I find more information about MarkerType? I am using a .png image from a custom url for the marker. The url is pointing to the .png file in my assets folder. The markerType I chose was ScaledImageMarker.

thor-schueler commented 6 years ago

Take a look at https://github.com/infusion-code/angular-maps/wiki/Samples, section Custom Marker Samples. ScaledImageMarker uses an actual image tag to load the image prior to scaling on the canvas. If the image is large, that might take some time ☺. If you do the id on the iconInfo and use the same image for all markers, it is not a big deal. However, if you use more than one, then moving to canvas or font-based is preferable.

Alternatively, you can simply use the IconUrl input on the marker component to set the url of your image without transform. If you do that, you got to make sure your images are the correct size through as you won’t be able to scale them. In that case, I would suggest going with SVG. Also, remember that you can also provide a data url in the IconUrl as well as do inline svg… This is quite handy and performs well.

Take a look at https://github.com/infusion-code/angular-maps/blob/master/src/models/marker.ts for the actual implementation of the various marker types supported by IMakerIconInfo…

drewteachout commented 6 years ago

Thanks a bunch. For my purposes I think [IconURL] is all that's necessary. You can go ahead and close this issue.