How to fit bounds for coordinate array with google maps sdk for iOS? - google-maps-sdk-ios

How to fit bounds for coordinate array with google maps sdk for iOS?
I need to zoom map for 4 visible markers.

Here's my solution for this problem. Building a GMSCoordinateBounds object by multiple coordinates.
- (void)focusMapToShowAllMarkers {
CLLocationCoordinate2D myLocation = ((GMSMarker *)_markers.firstObject).position;
GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithCoordinate:myLocation coordinate:myLocation];
for (GMSMarker *marker in _markers)
bounds = [bounds includingCoordinate:marker.position];
[_mapView animateWithCameraUpdate:[GMSCameraUpdate fitBounds:bounds withPadding:15.0f]];
}
Updated answer: Since GMSMapView markers property is deprecated, you should save all markers in your own array.
updated swift 3 answer:
func focusMapToShowAllMarkers() {
let firstLocation = (markers.first as GMSMarker).position
var bounds = GMSCoordinateBoundsWithCoordinate(firstLocation, coordinate: firstLocation)
for marker in markers {
bounds = bounds.includingCoordinate(marker.position)
}
let update = GMSCameraUpdate.fitBounds(bounds, withPadding: CGFloat(15))
self.mapView.animate(cameraUpdate: update)
}

Swift 3.0 version of Lirik's answer:
func focusMapToShowAllMarkers() {
let myLocation: CLLocationCoordinate2D = self.markers.first!.position
var bounds: GMSCoordinateBounds = GMSCoordinateBounds(coordinate: myLocation, coordinate: myLocation)
for marker in self.markers {
bounds = bounds.includingCoordinate(marker.position)
self.mapView.animate(with: GMSCameraUpdate.fit(bounds, withPadding: 15.0))
}
}
And here's my own way:
func focusMapToShowMarkers(markers: [GMSMarker]) {
guard let currentUserLocation = self.locationManager.location?.coordinate else {
return
}
var bounds: GMSCoordinateBounds = GMSCoordinateBounds(coordinate: currentUserLocation,
coordinate: currentUserLocation)
_ = markers.map {
bounds = bounds.includingCoordinate($0.position)
self.mapView.animate(with: GMSCameraUpdate.fit(bounds, withPadding: 15.0))
}
}
And you can call my function above like so:
self.focusMapToShowMarkers(markers: [self.myLocationMarker, currentPokemonMarker])

Swift 5 version of Lirik's answer:
func focusMapToShowAllMarkers() {
if arrMarkers.count > 0 {
let firstLocation = (arrMarkers.first!).position
var bounds = GMSCoordinateBounds(coordinate: firstLocation, coordinate: firstLocation)
for marker in arrMarkers {
bounds = bounds.includingCoordinate(marker.position)
}
let update = GMSCameraUpdate.fit(bounds, withPadding: CGFloat(15))
self.mapView.animate(with: update)
}
}

For time being, Google has finally implemented the GMSCoordinateBounds,
you can make use of it with GMSCameraUpdate.
For details, please check the official reference.

We can simplify this quite a bit, with code such as the following:
extension Array where Element: GMSMarker {
func encompassingCoordinateBounds() -> GMSCoordinateBounds {
reduce(GMSCoordinateBounds(), { $0.includingCoordinate($1.position) })
}
}
The call site would like:
let markers = [GMSMarker]()
let encompassingCoordinateBounds = markers.encompassingCoordinateBounds()

Related

How to move 3D model on Cesium

I wanted to move the model dynamically using keyboard shortcuts. I could not find relevant article on that.
So for now, I'm trying to move the model on click. When click on the model. The model has to move in one direction (increment the value 1 on tick). Find below the sandcastle code for that.
var selectedMesh; var i=0;
var viewer = new Cesium.Viewer('cesiumContainer', {
infoBox: false,
selectionIndicator: false
});
var handle = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
function createModel(url, height) {
viewer.entities.removeAll();
var position = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height);
var heading = Cesium.Math.toRadians(135);
var pitch = 0;
var roll = 0;
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, heading, pitch, roll);
var entity = viewer.entities.add({
name: url,
position: position,
orientation: orientation,
model: {
uri: url,
minimumPixelSize: 128
}
});
viewer.trackedEntity = entity;
viewer.clock.onTick.addEventListener(function () {
if (selectedMesh) {
console.log("Before 0 : " + selectedMesh.primitive.modelMatrix[12]);
selectedMesh.primitive.modelMatrix[12] = selectedMesh.primitive.modelMatrix[12] + 1;
console.log("After 0 : " + selectedMesh.primitive.modelMatrix[12]);
}
});
}
handle.setInputAction(function (movement) {
console.log("LEFT CLICK");
var pick = viewer.scene.pick(movement.position);
if (Cesium.defined(pick) && Cesium.defined(pick.node) && Cesium.defined(pick.mesh)) {
if (!selectedMesh) {
selectedMesh = pick;
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
var options = [{
text: 'Aircraft',
onselect: function () {
createModel('../../SampleData/models/CesiumAir/Cesium_Air.bgltf', 5000.0);
}
}, {
text: 'Ground vehicle',
onselect: function () {
createModel('../../SampleData/models/CesiumGround/Cesium_Ground.bgltf', 0);
}
}, {
text: 'Milk truck',
onselect: function () {
createModel('../../SampleData/models/CesiumMilkTruck/CesiumMilkTruck.bgltf', 0);
}
}, {
text: 'Skinned character',
onselect: function () {
createModel('../../SampleData/models/CesiumMan/Cesium_Man.bgltf', 0);
}
}];
Sandcastle.addToolbarMenu(options);
When I click, the model is moving for the first time. After that, It stays on the same place. I've printed the value in the console. It seems the value is not changing. I'm not sure about the problem here. or I'm implementing the transformation wrongly.
If you keep track of the current lat and lon of the entity, and adjust that lat and lon based on user input, all you need to do is update the orientation of the entity.
var lon = // the updated lon
var lat = // updated lat
var position = Cesium.Cartesian3.fromDegrees(lon, lat, height);
var heading = Cesium.Math.toRadians(135);
var pitch = 0;
var roll = 0;
// create an orientation based on the new position
var orientation = Cesium.Transforms.headingPitchRollQuaternion(position, heading, pitch, roll);
Then you just need to update the orientation of the entity.
entity.orientation = orientation;
By changing the value, the models orientation, and therefore position will get updated.

mapserver with WMS call with openlayers

I scenario is below :
map is shown under from TiledWMS layer from mapserver. It has 2 layers.
TiledWMS layer for OSM world map.
TiledWMS layer for layers defined in kml file placed in mapserver through .map file. This map file contains many layers.
Now , when user click on map : it got 2 layers as above.
But since 2nd layer is made up of different layers as given in .map file , i am not able to uniquely identify these layers. I want that since 2 nd layer is made up of different layers in kml file i should be able to uniquely identify them on mouse click or hower.
Thanks
Satpal
I am able to get it : below is samaple code for others.
var coord = evt.coordinate;
var pixel = $scope.map.getPixelFromCoordinate(coord);
var viewProjection = $scope.map.getView().getProjection();
var viewResolution = $scope.map.getView().getResolution();
var numberOfLayersOnMap = $scope.map.getLayers();
var feature = $scope.map.forEachFeatureAtPixel(pixel, function(feature, layer){return feature;}, null, function(layer) {return true;});
if(feature === undefined)
{
$scope.map.forEachLayerAtPixel(pixel, function (layer)
{
if(!layer)
{
return ;
}
var urlWMSGetFeatureInfo = layer.getSource().getGetFeatureInfoUrl(coord, viewResolution, viewProjection, {
'INFO_FORMAT': 'application/vnd.ogc.gml'
});
if(urlWMSGetFeatureInfo.indexOf("osm-google.map")<0)
{
$http({
method: 'GET',
url: urlWMSGetFeatureInfo,
}).success(function(data){
var parser = new ol.format.WMSGetFeatureInfo();
var features = parser.readFeatures(data);
if(features.length>0)
{
var featureName = features[0].n.Name;
topOverlayElement.innerHTML = featureName;
$scope.highlightOverlay.setFeatures(new ol.Collection());
if($scope.flagLinkage == true)
{
var xmlObj = utility.StringToXML(data);
var xmlDocumnet = xmlObj.childNodes[0];
var layerNode = xmlDocumnet.children[0];
var gmlLayerNode = layerNode.children[0];
var layerName = gmlLayerNode.textContent;
var layerInfoObject = {};
layerInfoObject.layerName = layerName;
//layerInfoObject.placemarkName = featureName;
$scope.placemarksSelectedObject.push(layerInfoObject);
$scope.placemarksSelectedFeatureObject.push(features[0]);
}
else
{
$scope.placemarksSelectedFeatureObject.length = 0;
$scope.placemarksSelectedFeatureObject.push(features[0]);
}
$scope.highlightOverlay.setFeatures(new ol.Collection($scope.placemarksSelectedFeatureObject));
var featureDescription = features[0].n.description;
middleOverlayElement.innerHTML = (featureDescription === undefined) ? '' : featureDescription;
$scope.showOverlay(coord);
}
}).error(function (data) {
console.log("Not able to get capabilty data.");
});
}
else
{
$scope.closeOverlay(evt);
}
});

No pushpin when using MapsTask on WP8

I can't see the pushpin using MapsTask, the map is ok, but no pushpin on the map, anyone knows why? thank you!
if (geo != null)
{
MapsTask mapsTask = new MapsTask();
mapsTask.Center = geo;
mapsTask.ZoomLevel = 15;
mapsTask.Show();
}
you can use mapTask.SearchTerm, the first result will show on the map with the pushpin.
mapsTask has few APIs to do more works.
If you want multiple pushpins or customize for more info,use map control and design it in xaml and .cs file.
here is a good example:
http://www.geekchamp.com/articles/windows-phone-drawing-custom-pushpins-on-the-map-control-what-options-do-we-have
hopes it helps.
ReverseGeocodeQuery query = new ReverseGeocodeQuery();
query.GeoCoordinate = new GeoCoordinate(lat, longitude);
query.QueryCompleted += (s, e) =>
{
if (e.Error != null)
return;
searchTerm = e.Result[0].Information.Address.Street; // task.SearchTerm will contain the result of address
};
Now , you have the search Term , in MapsTask class, just assign
MapsTask task = new MapsTask();
task.SearchTerm = searchTerm;
This should work fine.

Get the latlng of a marker on the map by tag

I have a panto marker function which requires the longitude and latitude variables. I have the ability to send the contact name as a variable.
The contact name is the tag for the marker i want to pan to. Is it possible for me to get the longitude and latitude of the marker by tag?
Here is my panto function
function pantoUser(lati,longi,i)
{
jQuery("#dispatcher").gmap3({
action: 'panTo',
args:[new google.maps.LatLng(lati,longi)],
zoom: 7
});
currentPoint = i;
jQuery("#dispatcher").css({
cursor: 'pointer'
});
jQuery('#markerTitle' + i + '').fadeIn({
duration: 200,
queue: false
}).animate({
bottom: "32px"
}, {
duration: 200,
queue: false
});
jQuery("#target").stop(true, true).fadeIn(1200).delay(500).fadeOut(1200);
jQuery("#dispatcher").css({
cursor: 'default'
});
jQuery('#markerTitle' + i + '').stop(true, true).fadeOut(2000, function() {
jQuery("#dispatcher").css({
bottom: "0"
})
jQuery("#target").stop(true, true).fadeIn(1200).delay(500).fadeOut(1200);
});
}
I was thinking of something like this?
function locateLastSpeaker(name) {
var lati = SOMEHOW GET IT
var longi = SOMEHOW GET IT
pantoUser(lati,longi,1)
}
EDIT after trying duncans solution!!
var stuMarkers = {};
function addMarker(i, lati, longi, id, name, state, datestring) {
var placename = name;
stuMarkers[placename].lat = lati;
stuMarkers[placename].lng = longi;
$('#dispatcher').gmap3(
{ action: 'addMarker', ....etc
Instead of an array as barry suggests, what about an object, keyed on the name. That way you won't even have to loop.
var stuMarkers = {}; // global variable outside of any function
// loop creating your markers
function addMarker(i, lati, longi, id, name, state, datestring) {
var placename = name;
stuMarkers[placename] = {};
stuMarkers[placename].lat = lati;
stuMarkers[placename].lng = longi;
stuMarkers[placename].i = i;
...
}
function locateLastSpeaker(name) {
pantoUser(stuMarkers[name].lat,stuMarkers[name].lng,stuMarkers[name].i);
}
Keep a reference to each marker - probably in an array.
When you want to find a particular marker, loop though that array, and find the particular marker.
Then do what you want with it.

Country name from latitude and longitude

This question may look familiar: I have the latitude and longitude of a place. I need to get the name of country. I know I have to use reverse geo coding for this. But my problem is that sometimes it returns the short form of the area or country (for example US for United State or CA for California). Is there any way that I can get the full name of the country? I can't perform a match operation by this short forms with my prestored country database.
I have already gone through this, this. But it's not much help for my problem.
The geocoder response usually returns several results which include street corners, intersections, counties, and other alternate representation names. I found that results[0] is the best description.
The trick is searching for "country" in the results. Then the long_name can be retrieved.
Click on the map
function getCountry(latLng) {
geocoder.geocode( {'latLng': latLng},
function(results, status) {
if(status == google.maps.GeocoderStatus.OK) {
if(results[0]) {
for(var i = 0; i < results[0].address_components.length; i++) {
if(results[0].address_components[i].types[0] == "country") {
alert(results[0].address_components[i].long_name);
}
}
}
else {
alert("No results");
}
}
else {
alert("Status: " + status);
}
}
);
}
The JSON array normally includes both long_name and short_name. You should be able to extract both...
Here is an JSON/XML parser that works for both google street maps and open street maps.
(the only problem is that it requires a JSON or XML object as the "reply" its tested on version 3 google and 0.6 open street maps and it works good)
NOTE: it returns an object location.lat or location.lon you can also have it return whatever other field you want.
JSON.parse(text) // where text is the reply from google or open street maps
XML.parse(text) // you can make your own to convert the reply to XML or use regex to parse it. If someone has a regex version to parse the text reply that may also be helpful.
// Parser(ajax reply object, google/open, json/xml);
// takes the reply from google maps or open street maps and creates an object with location[lat/lon]
function Parser(reply, provider, type) {
var location = {};
if(reply != null) {
if(provider == "google") { // Google Street Maps
switch(type) {
case "xml":
location["lat"] = reply.getElementsByTagName("lat")[0].textContent;
location["lon"] = reply.getElementsByTagName("lng")[0].textContent;
break;
default: // json
location["lat"] = reply.results[0].geometry.location.lat;
location["lon"] = reply.results[0].geometry.location.lng;
}
}
else { // Open Street Maps
switch(type) {
case "xml":
location["lat"] = reply.getElementsByTagName("place")[0].getAttribute("lat");
location["lon"] = reply.getElementsByTagName("place")[0].getAttribute("lon");
break;
default: // json
location["lat"] = reply[0].lat;
location["lon"] = reply[0].lon;
}
}
}
return location;
}
function getLocation() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
$.post("https://maps.googleapis.com/maps/api/geocode/json?latlng=" + position.coords.latitude + "," + position.coords.longitude + "&sensor=false", function (result) {
for (var i = 0; i < result['results'][0]['address_components'].length; i++) {
if (result['results'][0]['address_components'][i]['types'][0] == "country") {
alert(result['results'][0]['address_components'][i]['long_name']);
}
}
});
});
}
}
getLocation();