Mapsui - how to provide current user's location and focus map on it? - xamarin

I am following the quickstart at https://mapsui.com/documentation/getting-started-xamarin-forms.html and wonder if I need to obtain the location by myself (with e.g Xamarin.Essentials' Location routines) or the Mapsui will obtain one by itself.
I've marked the location priviledges in AndroidManifest, allowed the access but still the blue circle points at 0,0 coordinates.
EDIT: here's the code
public MainPage()
{
InitializeComponent();
map = new Mapsui.Map
{
CRS = "EPSG:3857",
Transformation = new MinimalTransformation(),
};
var tileLayer = OpenStreetMap.CreateTileLayer();
map.Layers.Add(tileLayer);
map.Widgets.Add(new Mapsui.Widgets.ScaleBar.ScaleBarWidget(map) {
TextAlignment = Mapsui.Widgets.Alignment.Center,
HorizontalAlignment = Mapsui.Widgets.HorizontalAlignment.Left,
VerticalAlignment = Mapsui.Widgets.VerticalAlignment.Bottom });
mapView.MyLocationEnabled = true;
mapView.PanLock = true;
mapView.Map = map;
}
Unfortunately, none of MyLocationEnabled and PanLock make any effect on the map - the blue circle is stil in 0,0 and the map isn't pan-locked.

Related

AmCharts AmMap - Set starting location for zoom actions

I would like to use the "zoomToMapObject" method based on a selection on a dropdown menu.
For some reason the start zoom location is the middle of the map and not the set the geoPoint.
(The zooming works but the start location make it look a bit weird.)
My current approach looks like this:
const duration = this.chart.zoomToMapObject(selectedPoloygon, this.countryZoom, true).duration;
setTimeout(() => {
this.chart.homeGeoPoint = geoPoint;
this.chart.homeZoomLevel = this.countryZoom;
}, duration);
this.handleCountrySelection(selectedPoloygon);
Somehow even setting the homeGeoPoint / homeZoomLevel doesn't affect next zoom actions.
**UPDATE: Workaround heavy cost (from 1300 nodes to over 9000) **
I examined the problem a step further. It seems the middle point gets set when I push a new mapImageSeries into the map.
My workarround currently is to draw all points on the map and hide them.
Then after I select a country I change the state to visible.
However this approach is very costly. The DOM-Nodes rises from 1300 to ~ 9100.
My other approach with creating them after a country has been selected AND the zoom animation finished was much more
effective. But due to the map starting every time for a center location it is not viable? Or did I do s.th. wrong?
Here is my current code which is not performant:
// map.ts
export class MapComponent implements AfterViewInit, OnDestroy {
imageSeriesMap = {};
// ... standard map initialization ( not in zone of course )
// creating the "MapImages" which is very costly
this.dataService.getCountries().forEach(country => {
const imageSeriesKey = country.id;
const imageSeriesVal = chart.series.push(new am4maps.MapImageSeries()); // takes arround 1-2 ms -> 300 x 2 ~ 500 ms.
const addressForCountry = this.dataService.filterAddressToCountry(country.id); // returns "DE" or "FR" for example.
const imageSeriesTemplate = imageSeriesVal.mapImages.template;
const circle = imageSeriesTemplate.createChild(am4core.Circle);
circle.radius = 4;
circle.fill = am4core.color(this.colorRed);
circle.stroke = am4core.color('#FFFFFF');
circle.strokeWidth = 2;
circle.nonScaling = true;
circle.tooltipText = '{title}';
imageSeriesTemplate.propertyFields.latitude = 'latitude';
imageSeriesTemplate.propertyFields.longitude = 'longitude';
imageSeriesVal.data = addressForCountry.map(address => {
return {
latitude: Number.parseFloat(address.lat),
longitude: Number.parseFloat(address.long),
title: address.company
};
});
imageSeriesVal.visible = false;
this.imageSeriesMap[imageSeriesKey] = imageSeriesVal;
});
// clicking on the map
onSelect(country) {
this.imageSeriesMap[country].visible = true;
setTimeout( () => {
const chartPolygons = <any>this.chart.series.values[0];
const polygon = chartPolygons.getPolygonById(country);
const anim = this.chart.zoomToMapObject(polygon, 1, true, 1000);
anim.events.on('animationended', () => {});
this.handleCountrySelection(polygon);
}, 100);
});
}
handleCountrySelection(polygon: am4maps.MapPolygon) {
if (this.selectedPolygon && this.selectedPolygon !== polygon) {
this.selectedPolygon.isActive = false;
}
polygon.isActive = true;
const geoPoint: IGeoPoint = {
latitude: polygon.latitude,
longitude: polygon.longitude
};
this.chart.homeGeoPoint = geoPoint;
this.chart.homeZoomLevel = this.countryZoom;
this.selectedPolygon = polygon;
}
}
Thanks to your thorough followup I was able to replicate the issue. The problem you were having is triggered by any one of these steps:
dynamically pushing a MapImageSeries to the chart
dynamically creating a MapImage via data (also please note in the pastebind you provided, data expects an array, I had to change that while testing)
In either step, the chart will fully zoom out as if resetting itself. I'm going to look into why this is happening and if it can be changed, so in the meantime let's see if the workaround below will work for you.
If we only use a single MapImageSeries set in advance (I don't particularly see a reason to have multiple MapImageSeries, would one not do?), that eliminates problem 1 from occurring. Asides from data, we can create() MapImages manually via mapImageSeries.mapImages.create(); then assign their latitude and longitude properties manually, too. With that, problem 2 does not occur either, and we seem to be good.
Here's a demo with a modified version of the pastebin:
https://codepen.io/team/amcharts/pen/c460241b0efe9c8f6ab1746f44d666af
The changes are that the MapImageSeries code is taken out of the createMarkers function so it only happens once:
const mapImageSeries = chart.series.push(new am4maps.MapImageSeries());
const imageSeriesTemplate = mapImageSeries.mapImages.template;
const circle = imageSeriesTemplate.createChild(am4core.Circle);
circle.radius = 10;
circle.fill = am4core.color('#ff0000');
circle.stroke = am4core.color('#FFFFFF');
circle.strokeWidth = 2;
circle.nonScaling = true;
circle.tooltipText = 'hi';
In this case, there's no need to pass chart to createMarkers and return it, so I've passed polygon instead just to demo dynamic latitude/longitudes, I also assign our new MapImage to the polygon's data (dataItem.dataContext) so we can refer to it later. Here's the new body of createMarkers:
function createMarkers(polygon) {
console.log('calling createMarkers');
if ( !polygon.dataItem.dataContext.redDot) {
const dataItem = polygon.dataItem;
// Object notation for making a MapImage
const redDot = mapImageSeries.mapImages.create();
// Note the lat/long are direct properties
redDot.id = `reddot-${dataItem.dataContext.id}`;
// attempt to make a marker in the middle of the country (note how this is inaccurate for US since we're getting the center for a rectangle, but it's not a rectangle)
redDot.latitude = dataItem.north - (dataItem.north - dataItem.south)/2;
redDot.longitude = dataItem.west - (dataItem.west - dataItem.east)/2;;
dataItem.dataContext.redDot = redDot;
}
}
There's no need for the animationended event or anything, it just works since there is no longer anything interfering with your code. You should also have your performance back.
Will this work for you?
Original answer prior to question's edits below:
I am unable to replicate the behavior you mentioned. Also, I don't know what this.countryZoom is.
Just using the following in a button handler...
chart.zoomToMapObject(polygon);
...seems to zoom just fine to the country, regardless of the current map position/zoomLevel.
If you need to time something after the zoom animation has ended, the zoomToMapObject returns an Animation, you can use its 'animationended' event, e.g.
const animation = this.chart.zoomToMapObject(selectedPoloygon, this.countryZoom, true);
animation.events.on("animationended", () => {
// ...
});
Here's an example with all that with 2 external <button>s, one for zooming to USA and the other Brazil:
https://codepen.io/team/amcharts/pen/c1d1151803799c3d8f51afed0c6eb61d
Does this help? If not, could you possibly provide a minimal example so we can replicate the issue you're having?

UPDATED: Javascript logic to fix in a small function (SVG, obtaining absolute coords)

NEW:
So here is the code at codepen:
http://codepen.io/cmer41k/pen/pRJNww/
Currently function UpdateCoords(draggable) - is commented out in the code.
What I wanted is to update on mouseup event the coordinates of the path (circle as path here) to the absolute ones and remove transform attribute.
But I am failing to do that;(( sorry only learning
OLD:
In my code I have an svg element (path) that gets dragged around the root svg obj (svg) via transform="translate(x,y)" property.
I wanted to update such path element's attribute "d" (the string that describes all coords) to use absolute coordinates and get rid of transformed\translate thing.
Basically:
was: d="M10,10 30,10 20,30" + transform="translate(20,0);
to be: d="M30,10 50,10 40,30" + transform="translate(0,0)" (or if we can delete the transform - even better)
So I did the code that does the thing for me, but there is a bug that prevents proper result.
I am sure I am doing something wrong in here:
var v = Object.keys(path.controlPoints).length
// controlPoints here is just a place in path object where I store the coords for the path.
var matrix = path.transform.baseVal.consolidate();
//I validated that the above line does give me proper transform matrix with proper x,y translated values. Now below I am trying to loop through and update all control points (coordinates) of the path
for (i=0; i<v; i++) {
var position = svg.createSVGPoint();
position.x = path.controlPoints["p"+i].x;
position.y = path.controlPoints["p"+i].y;
// so for each of path's control points I create intermediate svgpoint that can leverage matrix data (or so I think) to "convert" old coords into the new ones.
position = position.matrixTransform(matrix);
path.controlPoints["p"+i].x = position.x;
path.controlPoints["p"+i].y = position.y;
}
// I am sure I am doing something wrong here, maybe its because I am not "cleaning"/resetting this position thing in this loop or smth?
Sorry I am not a programmer, just learning stuff and the question is - in this code snipped provided the goal that I described - is something wrong with how I handle "position"?
Alright, the code snipped is now functioning properly!
So after I figured how to obtain properly the matrix I still had a weird displacement for any subsequent draggables.
I became clear that those displacements happen even before my function.
I debugged it a bit and realized that I was not clearing the ._x and ._y params that I use for dragging.
Now code works!
http://codepen.io/cmer41k/pen/XpbpQJ
var svgNS = "http://www.w3.org/2000/svg";
var draggable = null;
var canvas = {};
var inventory = {};
var elementToUpdate = {};
//debug
var focusedObj = {};
var focusedObj2 = {};
// to be deleted
window.onload = function() {
canvas = document.getElementById("canvas");
inventory = document.getElementById("inventory");
AddListeners();
}
function AddListeners() {
document.getElementById("svg").addEventListener("mousedown", Drag);
document.getElementById("svg").addEventListener("mousemove", Drag);
document.getElementById("svg").addEventListener("mouseup", Drag);
}
// Drag function //
function Drag(e) {
var t = e.target, id = t.id, et = e.type; m = MousePos(e); //MousePos to ensure we obtain proper mouse coordinates
if (!draggable && (et == "mousedown")) {
if (t.className.baseVal=="inventory") { //if its inventory class item, this should get cloned into draggable
copy = t.cloneNode(true);
copy.onmousedown = copy.onmouseup = copy.onmousemove = Drag;
copy.removeAttribute("id");
copy._x = 0;
copy._y = 0;
canvas.appendChild(copy);
draggable = copy;
dPoint = m;
}
else if (t.className.baseVal=="draggable") { //if its just draggable class - it can be dragged around
draggable = t;
dPoint = m;
}
}
// drag the spawned/copied draggable element now
if (draggable && (et == "mousemove")) {
draggable._x += m.x - dPoint.x;
draggable._y += m.y - dPoint.y;
dPoint = m;
draggable.setAttribute("transform", "translate(" +draggable._x+","+draggable._y+")");
}
// stop drag
if (draggable && (et == "mouseup")) {
draggable.className.baseVal="draggable";
UpdateCoords(draggable);
console.log(draggable);
draggable._x = 0;
draggable._y = 0;
draggable = null;
}
}

Scale UI for multiple resolutions/different devices

I have a quite simple unity GUI that has the following scheme :
Where Brekt and so are buttons.
The GUI works just fine on PC and is on screen space : overlay so it is supposed to be adapted automatically to fit every screen.
But on tablet the whole GUI is smaller and reduced in the center of the screen, with huge margins around the elements (can't join a screenshot now)
What is the way to fix that? Is it something in player settings or in project settings?
Automatically scaling the UI requires using combination of anchor,pivot point of RecTransform and the Canvas Scaler component. It is hard to understand it without images or videos. It is very important that you thoroughly understand how to do this and Unity provided full video tutorial for this.You can watch it here.
Also, when using scrollbar, scrollview and other similar UI controls, the ContentSizeFitter component is also used to make sure they fit in that layout.
There is a problem with MovementRange. We must scale this value too.
I did it so:
public int MovementRange = 100;
public AxisOption axesToUse = AxisOption.Both; // The options for the axes that the still will use
public string horizontalAxisName = "Horizontal"; // The name given to the horizontal axis for the cross platform input
public string verticalAxisName = "Vertical"; // The name given to the vertical axis for the cross platform input
private int _MovementRange = 100;
Vector3 m_StartPos;
bool m_UseX; // Toggle for using the x axis
bool m_UseY; // Toggle for using the Y axis
CrossPlatformInputManager.VirtualAxis m_HorizontalVirtualAxis; // Reference to the joystick in the cross platform input
CrossPlatformInputManager.VirtualAxis m_VerticalVirtualAxis; // Reference to the joystick in the cross platform input
void OnEnable()
{
CreateVirtualAxes();
}
void Start()
{
m_StartPos = transform.position;
Canvas c = GetComponentInParent<Canvas>();
_MovementRange = (int)(MovementRange * c.scaleFactor);
Debug.Log("Range:"+ _MovementRange);
}
void UpdateVirtualAxes(Vector3 value)
{
var delta = m_StartPos - value;
delta.y = -delta.y;
delta /= _MovementRange;
if (m_UseX)
{
m_HorizontalVirtualAxis.Update(-delta.x);
}
if (m_UseY)
{
m_VerticalVirtualAxis.Update(delta.y);
}
}
void CreateVirtualAxes()
{
// set axes to use
m_UseX = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyHorizontal);
m_UseY = (axesToUse == AxisOption.Both || axesToUse == AxisOption.OnlyVertical);
// create new axes based on axes to use
if (m_UseX)
{
m_HorizontalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(horizontalAxisName);
CrossPlatformInputManager.RegisterVirtualAxis(m_HorizontalVirtualAxis);
}
if (m_UseY)
{
m_VerticalVirtualAxis = new CrossPlatformInputManager.VirtualAxis(verticalAxisName);
CrossPlatformInputManager.RegisterVirtualAxis(m_VerticalVirtualAxis);
}
}
public void OnDrag(PointerEventData data)
{
Vector3 newPos = Vector3.zero;
if (m_UseX)
{
int delta = (int)(data.position.x - m_StartPos.x);
delta = Mathf.Clamp(delta, -_MovementRange, _MovementRange);
newPos.x = delta;
}
if (m_UseY)
{
int delta = (int)(data.position.y - m_StartPos.y);
delta = Mathf.Clamp(delta, -_MovementRange, _MovementRange);
newPos.y = delta;
}
transform.position = new Vector3(m_StartPos.x + newPos.x, m_StartPos.y + newPos.y, m_StartPos.z + newPos.z);
UpdateVirtualAxes(transform.position);
}

Flex - Easy way to set border on image

Is there an easy way to add a simple border to an image?
I'm loading in image thumbs, and would like to add a border at runtime, instead of having to edit all the thumbs.
I'm using Spark Image.
Thanks!
EDIT:
I need to add a 1 px white border around these thumbs. I set the size of the tumbs to be 90x90, to make them fit if they are either horisontal or vertical, but the actual images in my example scales down to 90x51 (this is not fixed, only 90x90 as a maximum is fixed)
This is my code for adding thumbNails to a TileGroup (loading the gallery from an xml file):
private function loadPopUpThumbs():void{
if(curThumbImg <= totThumbImg){
var thumbImg:Image = new Image();
var _loader:Loader = new Loader();
var imageNr:int = curThumbImg;
var thumbContainer:BorderContainer = new BorderContainer();
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE,function(e:Event):void{
thumbImg.source = e.currentTarget.content;
popUpImgGroup.addElement(thumbImg);
thumbImg.width = 90;
thumbImg.height = 90;
thumbImg.scaleMode = "letterbox";
thumbImg.verticalAlign = "bottom";
thumbImg.smooth = true;
thumbImg.id = "thumbImg" + imageNr;
//thumbImg.drawRoundRect(0,0,thumbImg.width,thumbImg.height, null, 0xffffff);
thumbImg.addEventListener(MouseEvent.CLICK, function(evt:MouseEvent):void{
popUpThumbClicked(imageNr.toString());
});
trace("Thumb added: " + popUpXMLList.(attribute('nr')==imageNr.toString()).#thumbURL);
curThumbImg++;
loadPopUpThumbs();
});
_loader.load(new URLRequest(encodeURI(popUpXMLList.(attribute('nr')==imageNr.toString()).#thumbURL)));
}else{
trace("DONE Adding thubs!!!");
}
}
Also: Would it be possible to add a linebreak in the items added to the TileGroup?
In my XML file I've defined a group attribute, so that I'm able to devide the images into groups. If I click a image from one group, I can skip next/prev within that group, but not to the next group. Is there any way for me to insert a linebreak to my TileGroup, so that I can listen for when the prevGroup != curGroup, and then add in some sort of spacing before continuing adding the next thumbs? All I need is a way to skip a line in the tileGroup :)
Thanks!
You can create new custom Image Class, extends spark Image. Very simple and clean. And set border size and color with css. See example:
package classes
{
import flash.display.CapsStyle;
import flash.display.JointStyle;
import flash.display.LineScaleMode;
import spark.components.Image;
public class ImageBorder extends Image
{
public function ImageBorder()
{
super();
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (imageDisplay && imageDisplay.bitmapData)
{
var borderSize:Number = getStyle("borderSize") || 0;
var borderColor:Number = getStyle("borderColor") || 0xffffff;
var half:Number = borderSize/2;
imageDisplay.left = imageDisplay.top = imageDisplay.right = imageDisplay.bottom = borderSize;
graphics.clear();
graphics.lineStyle(borderSize, borderColor, 1, false, LineScaleMode.NONE, CapsStyle.NONE, JointStyle.MITER);
graphics.moveTo(0, half);
graphics.lineTo(unscaledWidth -half, half);
graphics.lineTo(unscaledWidth - half, unscaledHeight-half);
graphics.lineTo(half, unscaledHeight-half);
graphics.lineTo(half, half);
}
}
}
}
In application use with css:
<fx:Style>
#namespace s "library://ns.adobe.com/flex/spark";
#namespace mx "library://ns.adobe.com/flex/mx";
#namespace classes "classes.*";
classes|ImageBorder
{
borderSize : 10;
borderColor : "0xff00ff";
}
</fx:Style>
<classes:ImageBorder source=" your source url " />
Spark image is a SkinnableComponent.
You can create your custom skin that supports borders in any
convenient way, like styles or properties.
Or you can set that
skin if you want to see border or remove it if you don't want
Or you can put it inside BorderContainer, and set borderVisible to
true when you want to see the border.

In Android Maps V2, is there a way to control the positioning of the map when a marker is tapped on?

Currently, by default, when tapping a marker, the map centers on the marker. Is there a way to control that, introduce some offset values. I have a popup info window that is a little taller at times and I would like to position the map so it does not get cut off at the top.
You could likely override the GoogleMap marker click event and adjust the camera there.
For example
Maker lastOpened = null;
mMap.setOnMarkerClickListener(new OnMarkerClickListener() {
public boolean onMarkerClick(Marker marker) {
// Check if there is an open info window
if (lastOpened != null) {
// Close the info window
lastOpened.hideInfoWindow();
// Is the marker the same marker that was already open
if (lastOpened.equals(marker)) {
// Nullify the lastOpened object
lastOpened = null;
// Return so that the info window isn't opened again
return true;
}
}
// Open the info window for the marker
marker.showInfoWindow();
// Re-assign the last opened such that we can close it later
lastOpened = marker;
// Get the markers current position
LatLng curMarkerPos = marker.getPosition();
// Use the markers position to get a new latlng to move the camera to such that it adjusts appropriately to your infowindows height (might be more or less then 0.3 and might need to subtract vs add this is just an example)
LatLng camMove = new LatLng(curMarkerPos.latitude + 0.3, curMarkerPos.longitude);
// Create a camera update with the new latlng to move to
CameraUpdate camUpdate = CameraUpdateFactory.newLatLng(camMove);
// Move the map to this position
mMap.moveCamera(camUpdate);
// Event was handled by our code do not launch default behaviour.
return true;
}
});
mMap.setOnMapClickListener(new OnMapClickListener() {
#Override
public void onMapClick(LatLng point) {
if (lastOpened != null) {
// Hide the last opened
lastOpened.hideInfoWindow();
// Nullify lastOpened
lastOpened == null;
}
// Move the camera to the new position
final CameraPosition cameraPosition = new CameraPosition.Builder().target(point).zoom(mMap.getCameraPosition().zoom).build();
mMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
});
This code hasnt been tested but should at least give you a great start at it. The default behavior of onMarkerClick is to move the camera and open the info window. So overriding this and implementing your own should allow you to move the camera where u please.
Thanks, DMan
I modified the really nice answer above for anyone who just wants to remove the move-to-center default behavior of the marker click:
Add this to setUpMap():
mMap.setOnMarkerClickListener(getMarkerClickListener());
And then add method:
Marker lastOpened = null;
public OnMarkerClickListener getMarkerClickListener() {
return new OnMarkerClickListener() {
public boolean onMarkerClick(Marker marker) {
if (lastOpened != null) {
lastOpened.hideInfoWindow();
if (lastOpened.equals(marker)) {
lastOpened = null;
return true;
}
}
marker.showInfoWindow();
lastOpened = marker;
return true;
}
};
}

Resources