nativescript-google-maps-sdk icon indicating copy to clipboard operation
nativescript-google-maps-sdk copied to clipboard

Auto-center map with multiple markers

Open Yamilquery opened this issue 7 years ago • 30 comments

How can I use fitBounds and google.maps.LatLngBounds()?

What I'm looking for is a way to avoid having to "manually" find the center of the map with center: new google.maps.LatLng(41.923, 12.513). Is there a way to automatically have the map centered on the 3 coordinates?

Yamilquery avatar Dec 10 '16 00:12 Yamilquery

@Yamilquery i need this can you update here if you found solution

moayadnajd avatar Dec 15 '16 16:12 moayadnajd

This is how I made it work, hope it helps!

Android: Once all the markers are on the map:

var builder = new com.google.android.gms.maps.model.LatLngBounds.Builder(); mapView.findMarker(function (marker) { builder.include(marker.android.getPosition()); }); var bounds = builder.build(); var cu = com.google.android.gms.maps.CameraUpdateFactory.newLatLngBounds(bounds,padding); mapView.gMap.animateCamera(cu);

iOS: Before start the placing the markers: var bounds = GMSCoordinateBounds.alloc().init();

While placing the markers (in the loop): bounds = bounds.includingCoordinate(marker[i].position);

After placing the markers: var update = GMSCameraUpdate.fitBoundsWithPadding(bounds, padding); mapView.gMap.moveCamera(update);

vtrend avatar Jan 31 '17 22:01 vtrend

@vtrend I got JS: ERROR TypeError: Cannot read property 'maps' of undefined trying to implementing your solution...

Any suggestion?

dianjuar avatar May 24 '17 03:05 dianjuar

can you share part of your code ?

Here is the reference for google maps on android as I assume is where you are having issues.

https://developers.google.com/android/reference/com/google/android/gms/maps/package-summary

vtrend avatar May 24 '17 16:05 vtrend

@vtrend the problem is on my workstation maybe...

declare var com:any;

console.log('------------------');
console.log(com.android);
console.log(com.android.gms);
console.log('------------------');

And in my console I have...

JS: ------------------
JS: [object Object]
JS: undefined
JS: ------------------

As you can see gms is not there.

I installed tns-platform-declarations to have autocompletion and is super nice but gms just doesn't appear...

I need to install something to have gms ?

dianjuar avatar May 24 '17 20:05 dianjuar

Before going deeper this is what I would do.

remove node_modules and npm install, then:

tns platform remove android
tns platform add android

if that doesn't work:

npm cache clean
npm remove -g nativescript
npm remove -g nativescript (yes twice)
npm install -g nativescript

Side note, I was having some issues with android 3.0.0 and I downgraded it to 2.5.0 to make it work.

vtrend avatar May 24 '17 20:05 vtrend

@vtrend My problem was really silly. I post it in the {N} forum and was solved.

I was doing com.android.gms instead com.google.android.gms.

I can implement your gist on {N}-Angular application, but I had to change some things to make it happen.

/** Platforms */
import {isAndroid, isIOS} from "platform";
/** Google Maps */
import * as mapsModule from "nativescript-google-maps-sdk";

declare var com:any;

/**
 * Variable to store the mapView object
 */
private _mapView: mapsModule.MapView;

/**
 * Funnction will trigger when the map is ready
 * @param args
 */
onMapReady(args: any) {
    this._mapView = args.object as mapsModule.MapView;
}

/**
 * Do the zoom to include points
 */
private _doZoom() {

    if( isAndroid ) {

        let builder = new com.google.android.gms.maps.model.LatLngBounds.Builder();
        this._mapView.findMarker( (marker) => {
            builder.include(marker.android.getPosition());
            return false;
        });

        let bounds = builder.build();
        let padding: number = 100;
        let cu = com.google.android.gms.maps.CameraUpdateFactory.newLatLngBounds(bounds, padding);
        this._mapView.gMap.animateCamera(cu);
    }
}

So you need to call _doZoom() when your makers are set.

Example

example gif


I'm using the lasted {N} version... 3.0.1 Nice work!

I have no environment to test your IOS gist for now... When I have it I will put the result

dianjuar avatar May 26 '17 20:05 dianjuar

Anyone know how to get this working on iOS? I can't figure out how to access GMSCoordinateBounds or GMSCameraUpdate.

mrhirsch avatar Oct 21 '17 16:10 mrhirsch

You can just access directly like:

let bounds = GMSCoordinateBounds.alloc().init();

In each position: bounds = bounds.includingCoordinate(position);

And then: let update = GMSCameraUpdate.fitBoundsWithPadding(bounds, padding); mapView.gMap.moveCamera(update);

vtrend avatar Oct 23 '17 16:10 vtrend

@vtrend solution worked(IOS) but for some reason it zooms in to maximum level when the camera position is updated.

I used setTarget instead and seems to work fine.

declare const GMSCameraUpdate: any;

//CLLocationCoordinate2DMake is available from 'tns-platform-declarations' let locationToSet = CLLocationCoordinate2DMake(latitude, longitude); let updateLocationCamera = GMSCameraUpdate.setTarget(locationToSet);

this.mapView.gMap.animateWithCameraUpdate(updateLocationCamera); or this.mapView.gMap.moveCamera(updateLocationCamera);

dimitriospafos avatar Nov 21 '17 01:11 dimitriospafos

Which import I need for GMSCoordinateBounds or CLLocationCoordinate2DMake ? I don't find it. Thanks.

learwebit avatar Mar 08 '18 11:03 learwebit

You don't need to import it, it's available for you already from this file objc!CoreLocation.d.ts which is located in tns-platform-declarations -> ios -> objc-i386

dimitriospafos avatar Mar 09 '18 02:03 dimitriospafos

Late to the party, but if it helps anyone, to get the viewport animation smooth on iOS you can use var update = GMSCameraUpdate.fitBoundsWithPadding(bounds, padding); mapView.gMap.animateWithCameraUpdate(update);

ogmedia avatar May 22 '18 06:05 ogmedia

@dimitriospafos It sounds like you got a solution for your problem, but I was running into your original problem of the map zooming out until I realized that it's because the padding parameter infitBoundsWithPadding has to be a number and not an array like the default [40, 40, 40, 40] that the example uses. My code looks like this:

let update = GMSCameraUpdate.fitBoundsWithPadding(this.iosMapBounds, 40);
this.mapView.gMap.animateWithCameraUpdate(update);

lin-brian-l avatar Jun 11 '18 23:06 lin-brian-l

How can we control the speed of the camera movement? The camera movement is smooth on Android, but on iOS, even after using the animateWithCameraUpdate method, the camera moves relatively faster than on an Android.

saibbyweb avatar Jan 18 '19 10:01 saibbyweb

I'm having issues with zooming to bounds on iOS. When I try the two above examples for iOS i get the message "Cannot find name CLLocationCoordinate2DMake / GMSCoordinateBounds". What am I missing?

Kraften avatar Jan 30 '19 12:01 Kraften

I'm having issues with zooming to bounds on iOS. When I try the two above examples for iOS i get the message "Cannot find name CLLocationCoordinate2DMake / GMSCoordinateBounds". What am I missing?

Did you declare those variables outside of your map component? I have something like this:

declare var GMSCoordinateBounds: any;

@Component({
...

lin-brian-l avatar Feb 01 '19 16:02 lin-brian-l

Thanks a lot @lin-brian-l ! I did not know how to work with native classes, and could not find anything about it in the docs. But how do i center on more than one marker? My code looks like this:

        var bounds = GMSCoordinateBounds.alloc().init();

        mapView.findMarker((marker) => {
            bounds = bounds.includingCoordinate(marker.position);
            return false;
        });
        var update = GMSCameraUpdate.fitBoundsWithPadding(bounds, padding);
        mapView.gMap.moveCamera(update);
    }

It centers on the maps with one makrer but not with maps with more.

Kraften avatar Feb 04 '19 08:02 Kraften

Hey @Kraften, if you want to center the map around plotted markers, I suggest using bounds.includingCoordinate() as they're plotted and not after they're plotted; to my knowledge, the mapView's array of markers is not a public attribute, so you cannot access it. I'm doing something like this:

this.mapView.removeAllMarkers(); // only if you need to constantly plot and re-plot markers

if (isIOS) this.iosMapBounds = GMSCoordinateBounds.alloc().init(); // initialize ios map boundary

markerDataArray.forEach(markerData => {
    let marker = new Marker();
    marker.position = Position.positionFromLatLng(data.latitude, data.longitude);
    
    //  define marker in ios map
    if (this.iosMapBounds) {
        let locationToSet = CLLocationCoordinate2DMake(marker.position.ios.latitude, marker.position.ios.longitude);
        this.iosMapBounds = this.iosMapBounds.includingCoordinate(locationToSet);
    }
    
    this.mapView.addMarker(marker);
});

let update = GMSCameraUpdate.fitBoundsWithPadding(this.iosMapBounds, 40);
this.mapView.gMap.animateWithCameraUpdate(update);

lin-brian-l avatar Feb 05 '19 15:02 lin-brian-l

Thanks again for helping me @lin-brian-l. I tried to implement you suggestion but it did not work. I might be doing things a bit different than you? This is what my whole class looks like.

export class MapComponent {
    @Input() public widget: MapWidget;
    private mapView$ = new BehaviorSubject<MapView | null>(null);

    constructor(private readonly meterMetaDataService: MeterMetaDataService) {
        this.mapView$.pipe(filter(mapView => mapView != null), withLatestFrom(this.meterMetaDataService.getMeters()), first())
            .subscribe(([mapView, meters]) => {
                const markers = Object.keys(this.widget.settings.colors)
                    .filter(meterId => meters[meterId] && meters[meterId].latitude && meters[meterId].longitude)
                    .map(meterId => {
                        const mapMarker: MapMarker = {
                            id: meters[meterId].id,
                            name: meters[meterId].name,
                            long: meters[meterId].longitude,
                            lat: meters[meterId].latitude,
                            color: this.widget.settings.colors[meterId]
                        };
                        return mapMarker;
                    })
                this.addMarkerToMap(mapView, markers);
                this.zoomCameraToBounds(mapView);
            });
    }

    public loadWhenMapIsReady(event: any): void {
        const mapView = event.object;
        // mapView.setStyle(MAP_STLE_JSON);
        this.mapView$.next(mapView);
    }

    private zoomCameraToBounds(mapView: MapView): void {
        const padding = 100;
        if (isAndroid) {
            const builder = new com.google.android.gms.maps.model.LatLngBounds.Builder();
            mapView.findMarker((marker) => {
                builder.include(marker.android.getPosition());
                return false;
            });
            const androidBounds = builder.build();
            const cameraUpdate = com.google.android.gms.maps.CameraUpdateFactory.newLatLngBounds(androidBounds, padding);
            mapView.gMap.animateCamera(cameraUpdate);
        } else if (isIOS) {
            var iosMapBounds = GMSCoordinateBounds.alloc().init();
            mapView.findMarker(marker => {
                if (iosMapBounds) {
                    let locationToSet = CLLocationCoordinate2DMake(marker.position.ios.latitude, marker.position.ios.longitude);
                    iosMapBounds = iosMapBounds.includingCoordinate(locationToSet);
                }
                return false
            })
            var update = GMSCameraUpdate.fitBoundsWithPadding(iosMapBounds, padding);
            mapView.gMap.animateWithCameraUpdate(update);
        }
    }

   private addMarkerToMap(mapView: MapView, mapMarkers: Array<MapMarker>): void {
        mapMarkers.map(mapMarker => {
            const marker = new Marker();
            marker.position = Position.positionFromLatLng(mapMarker.lat, mapMarker.long);
            marker.title = mapMarker.name;
            marker.color = mapMarker.color;
            mapView.addMarker(marker);

        });
    }
}

interface MapMarker {
    id: string;
    name: string;
    long: number;
    lat: number;
    color: string;
}

Kraften avatar Feb 06 '19 10:02 Kraften

Hey @Kraften, it looks like you're defining the iosMapBounds after you have plotted the marker; what went wrong when you tried adding the markers to iosMapBounds as you add the marker to the map (like inside addMarkerToMap()?

lin-brian-l avatar Feb 06 '19 12:02 lin-brian-l

If i change the method addMarkerToMap() so that i define the iosMapBounds before plotting the markers i get the map fully zoomed out with the markers in the middle but i also want it to zoom in as much as possible.

private addMarkerToMap(mapView: MapView, mapMarkers: Array<MapMarker>): void {
        this.iosMapBounds = GMSCoordinateBounds.alloc().init();
        mapMarkers.map(mapMarker => {
            const marker = new Marker();
            marker.position = Position.positionFromLatLng(mapMarker.lat, mapMarker.long);
            marker.title = mapMarker.name;
            marker.color = mapMarker.color;

            let locationToSet = CLLocationCoordinate2DMake(marker.position.ios.latitude, marker.position.ios.longitude);
            this.iosMapBounds = this.iosMapBounds.includingCoordinate(locationToSet);
            mapView.addMarker(marker);
            
        });
        var update = GMSCameraUpdate.fitBoundsWithPadding(this.iosMapBounds, 40);
        mapView.gMap.animateWithCameraUpdate(update);
    }

This is the result: simulator screen shot - iphone 6 - 2019-02-06 at 14 11 32

Kraften avatar Feb 06 '19 13:02 Kraften

Has anyone figured out the zooming issue? I can get it to view the bounds but it zooms all the way out. If you add minZoom it will zoom out to the minZoom, but thats still not very helpful.

edit: I am using padding but its still zoomed all the way out

let update = GMSCameraUpdate.fitBoundsWithPadding(bounds, 40);
mapView.gMap.moveCamera(update);

bakerac4 avatar Jul 11 '19 14:07 bakerac4

Has anyone figured out the zooming issue? I can get it to view the bounds but it zooms all the way out. If you add minZoom it will zoom out to the minZoom, but thats still not very helpful.

edit: I am using padding but its still zoomed all the way out

let update = GMSCameraUpdate.fitBoundsWithPadding(bounds, 40);
mapView.gMap.moveCamera(update);

A workaround that "solves" this issue is the following:

      const updateBounds = GMSCameraUpdate.fitBounds(boundsIOS);
      this.mapView.gMap.moveCamera(updateBounds);

      const updateZoom = GMSCameraUpdate.zoomTo(this.zoom);
      this.mapView.gMap.moveCamera(updateZoom);

AlessandroFBK avatar Sep 05 '19 09:09 AlessandroFBK

I found that I had to put the moveCamera() call in setTimeout for it to take effect if the map is being draw in a page being navigated to:

const update = GMSCameraUpdate.fitBounds(bounds)

setTimeout(() => {
  this.mapView.gMap.moveCamera(update)
}, 100)

aparajita avatar Sep 15 '19 06:09 aparajita

in Ios, if i use this method when tap a cluster to see the markers inside this, it doesn't center the map between the markers, it centers in the cluster tapped. Does anyone know how to fix it?

var bounds = GMSCoordinateBounds()

    locations.forEach { (clusterItem) in
        bounds = bounds.includingCoordinate(marker.position)
    }

    let update = GMSCameraUpdate.fit(bounds, withPadding: 80)
    self.mapView.animate(with: update)

Perchita avatar Oct 02 '20 09:10 Perchita

I have errors when trying to follow the examples above. NS 7.0.11 Angular CLI: 10.2.0 Node: 12.19.0 OS: darwin x64 Angular: 10.1.6 Initially I had Class constructor View cannot be invoked without 'new' I tried this soln #433 (comment) unzip map.zip and replace the same files in /node_modules/nativescript-google-maps-sdk But then I have problem in the next stage when looking to "Auto-center map with multiple markers" with bound()

ERROR in src/app/monitor/monitor.component.ts:136:50 - error TS2339: Property 'gms' does not exist on type 'typeof android'.

136 let builder = new com.google.android.gms.maps.model.LatLngBounds.Builder();

src/app/monitor/monitor.component.ts:143:41 - error TS2339: Property 'gms' does not exist on type 'typeof android'.

143 let cu = com.google.android.gms.maps.CameraUpdateFactory.newLatLngBounds(bounds, padding);

src/app/monitor/monitor.component.ts:146:32 - error TS2304: Cannot find name 'GMSCoordinateBounds'.

146 var iosMapBounds = GMSCoordinateBounds.alloc().init();

src/app/monitor/monitor.component.ts:154:26 - error TS2304: Cannot find name 'GMSCameraUpdate'.

154 var update = GMSCameraUpdate.fitBoundsWithPadding(iosMapBounds, padding);

Surely something must be very generic ... as it is seem that everyone got it passed this stage... easily
I can view map, place marker no problem except it complained 'gms' does not exist on type 'typeof android' ..


"dependencies": {
    "@angular/animations": "~10.1.0",
    "@angular/common": "~10.1.0",
    "@angular/compiler": "~10.1.0",
    "@angular/core": "~10.1.0",
    "@angular/forms": "~10.1.0",
    "@angular/platform-browser": "~10.1.0",
    "@angular/platform-browser-dynamic": "~10.1.0",
    "@angular/router": "~10.1.0",
    "@nativescript/angular": "~10.1.0",
    "@nativescript/background-http": "file:../ns-plugins/dist/packages/background-http",
    "nativescript-google-maps-sdk": "file:./src/app/nativescript-google-maps-sdk",
    "@nativescript/core": "~7.0.0",
    "@nativescript/geolocation": "^7.0.0",
    "@nativescript/theme": "~2.3.0",
    "@nstudio/nativescript-checkbox": "^2.0.3",
    "@nstudio/nativescript-snackbar": "^2.0.2",
    "moment": "^2.29.1",
    "moment-timezone": "^0.5.31",
    "nativescript-audio": "^6.1.0",
    "nativescript-background-http": "^4.2.1",
    "nativescript-barcodescanner": "^4.0.1",
    "nativescript-exit": "^1.0.1",
    "nativescript-mediafilepicker": "^4.0.1",
    "nativescript-permissions": "^1.3.9",
    "nativescript-qr-generator": "^2.0.0",
    "nativescript-ui-autocomplete": "^7.0.2",
    "nativescript-ui-chart": "^8.0.2",
    "nativescript-ui-listview": "~9.0.4",
    "nativescript-ui-sidedrawer": "^9.0.3",
    "nativescript-videoplayer": "^5.0.1",
    "nativescript-websockets": "^1.5.3",
    "reflect-metadata": "~0.1.12",
    "rxjs": "^6.6.0",
    "zone.js": "~0.11.1"
  },
  "devDependencies": {
    "@angular/compiler-cli": "~10.1.0",
    "@nativescript/android": "7.0.0",
    "@nativescript/ios": "7.0.1",
    "@nativescript/types": "~7.0.0",
    "@nativescript/webpack": "~3.0.0",
    "@ngtools/webpack": "~10.1.0",
    "node-sass": "^4.7.1",
    "tslint": "~5.19.0",
    "typescript": "~3.9.0"
  },

ebizcoAU avatar Nov 08 '20 05:11 ebizcoAU

@ebizcoAU are declaring com and GMSCameraUpdate ? if not add the below two lines outside your component code:

e.g :

import { View } from "ui/core/view";

declare const com: any; declare const GMSCameraUpdate: any;

export class MonitorComponent {

}

dimitriospafos avatar Nov 09 '20 00:11 dimitriospafos

Awesome!!! Thanks dimitriospafos - After adding .. import { View } from "tns-core-modules/ui/core/view"; declare const com: any; declare const GMSCameraUpdate: any; declare const GMSCoordinateBounds : any;

It works... great !!

ebizcoAU avatar Nov 10 '20 15:11 ebizcoAU

@ebizcoAU Great, glad it worked!

dimitriospafos avatar Nov 10 '20 17:11 dimitriospafos