In the WinRt/WP 8.1 MapControl, how do I differentiate between when the user changed the center of the screen by swiping vs a programmatic change?
The WinRt/WP 8.1 MapControl has a CenterChanged event ( http://msdn.microsoft.com/en-us/library/windows.ui.xaml.controls.maps.mapcontrol.centerchanged.aspx ), but this does not provide any info about what caused the center change.
Is there any other way of knowing whether or not the user changed the map center?
/* To give some more context, my specific scenario is as follows:
Given an app which shows a map, I want to track the gps position of a user.
If a gps position is found, I want to put a dot on the map and center the map to that point.
If a gps position change is found, I want to center the map to that point.
If the user changes the position of the map by touch/swipe, I no longer want to center the map when the gps position changes.
I could hack this by comparing gps position and center, but he gps position latLng is a different type & precision as the Map.Center latLng. I'd prefer a simpler, less hacky solution.
*/
I solved this by setting a bool ignoreNextViewportChanges to true before calling the awaitable TrySetViewAsync and reset it to false after the async action is done.
In the event handler, I immediately break the Routine then ignoreNextViewportChanges still is true.
So in the end, it looks like:
bool ignoreNextViewportChanges;
public void HandleMapCenterChanged() {
Map.CenterChanged += (sender, args) => {
if(ignoreNextViewportChanges)
return;
//if you came here, the user has changed the location
//store this information somewhere and skip SetCenter next time
}
}
public async void SetCenter(BasicGeoposition center) {
ignoreNextViewportChanges = true;
await Map.TrySetViewAsync(new Geopoint(Center));
ignoreNextViewportChanges = false;
}
If you have the case that SetCenter might be called twice in parallel (so that the last call of SetCenter is not yet finished, but SetCenter is called again), you might need to use a counter:
int viewportChangesInProgressCounter;
public void HandleMapCenterChanged() {
Map.CenterChanged += (sender, args) => {
if(viewportChangesInProgressCounter > 0)
return;
//if you came here, the user has changed the location
//store this information somewhere and skip SetCenter next time
}
}
public async void SetCenter(BasicGeoposition center) {
viewportChangesInProgressCounter++;
await Map.TrySetViewAsync(new Geopoint(Center));
viewportChangesInProgressCounter--;
}
Related
I have an app that shows a Map and a Pin on the center of it(just like Uber and PedidosYa), I have a button that when I click it sends the location where the pin's on. And it makes to appear the closest stores around that pin.
My problem is that when the first time the map appears its centered in my location, I move the map around to locate the pin, and when I click the button I want the map to stay there, but its comeback to the prior location and THEN moves the camera to the location where I drop the pin. I want to avoid that moving.
The function I use when I click the button to drop the pin is something like this:
var CenterPos = customMap.GetMapCenterLocation();
var pinPersonal = new CustomPin()
{
Id = "000",
Position = new Position(CenterPos.Latitude, CenterPos.Longitude),
Label = "Mio",
Url = "Mío"
};
customMap.Pins.Add(pinPersonal);
This draws a pin where I click the button. If I keep it this way, it draws the pin, and the camera comesback to the prior location.
After I use something like this:
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(
new Position(latitud, longitud), Distance.FromMiles(0.2)));
that makes the camera to move to the location I choose. But it comebacks always to the prior location and the moves to the new one.
Any idea? Im not sure from where this behavior comes.
Did you implement CustomMap according with Customizing a Map Pin - Xamarin?
Probably you should override OnMarkerClickListener.OnMarkerClick and return true in your custom renderer.
This means disable default behavior(centering map, open info-window) and you can implement your own behavior when pin clicked.
See this link.
Markers | Maps SDK for Android | Google Developers
/** Called when the user clicks a marker. */
#Override
public boolean onMarkerClick(final Marker marker) {
// Retrieve the data from the marker.
Integer clickCount = (Integer) marker.getTag();
// Check if a click count was set, then display the click count.
if (clickCount != null) {
clickCount = clickCount + 1;
marker.setTag(clickCount);
Toast.makeText(this,
marker.getTitle() +
" has been clicked " + clickCount + " times.",
Toast.LENGTH_SHORT).show();
}
// Return false to indicate that we have not consumed the event and that we wish
// for the default behavior to occur (which is for the camera to move such that the
// marker is centered and for the marker's info window to open, if it has one).
return false;
}
Using examples I found, I was able to connect to a webservice, download a world map and have it display properly in a JMapPane. My goal is to have the JMapPane only display a very specific portion of the world.
I have a geographic CRS based on NAD83 and UTM 17N. I prefer to use Northing and Easting values, for which I have the bounds of the area I wish to display.
Here is the relevant code:
Rectangle bounds=projectdata.getPlanRange();
ReferencedEnvelope envelope = new ReferencedEnvelope(bounds.getMinX(), bounds.getMaxX(), bounds.getMinY(), bounds.getMaxY(), projectdata.getWorkPlancrs());
mapPane.setDisplayArea(envelope);
If I don't set the displayarea with the envelope, I see the world. If I set it, it gives me a view of water somewhere in the world. I can only assume that the ReferencedEnvelope can handle Northing/Easting values with a geographic CRS provided. I've read the documentation a few times and searched a long time before posting this question. I'm sure there is a simple answer as to what I'm doing wrong.
In case it matters at all, the wms source I am using is
http://atlas.gc.ca/cgi-bin/atlaswms_en?VERSION=1.1.1&Request=GetCapabilities&Service=WMS
You don't say how you are creating your map in the JFrame. But the following works for me (subject to some caveats I'll go into below). The trick is to set the viewport with the CRS and the Bounding Box you require. You can get the full code here.
private void createWMS() {
content.getViewport().setCoordinateReferenceSystem(crs);
content.getViewport().setBounds(bbox);
if (url == null) {
throw new IllegalStateException("URL is not set");
}
try {
wms = new WebMapServer(url);
} catch (ServiceException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.exit(2);
}
WMSCapabilities capabilities = wms.getCapabilities();
org.geotools.data.ows.Layer[] wmsLayers = WMSUtils.getNamedLayers(capabilities);
for (org.geotools.data.ows.Layer wmsLayer : wmsLayers) {
if(layers.isEmpty()) {//just write out layers
System.out.println(wmsLayer.getName());
}
if (layers.contains(wmsLayer.getName())) {
WMSLayer displayLayer = new WMSLayer(wms, wmsLayer);
if (!styles.isEmpty()) {
String wmsStyle = styles.get(layers.indexOf(wmsLayer.getName()));
displayLayer = new WMSLayer(wms, wmsLayer);
List<StyleImpl> layerStyles = wmsLayer.getStyles();
for (StyleImpl s : layerStyles) {
if (s.getName().equalsIgnoreCase(wmsStyle)) {
displayLayer.setStyle((Style) s);
}
}
}
content.addLayer(displayLayer);
}
}
}
This produces with the CRS set to EPSG:26917 and a bbox of roughly -157051,5400992, 279239,5664905 of a map like:
As you can see this service is ending soon! and also it doesn't handle EPSG:26917 very well as it has drawn the "back" of Russia on the other side of the globe and labelled it before drawing Canada which is a bit confusing and if you zoom out much further the map becomes unusable due to the borders and rivers etc crossing "behind" the map.
I have got some Rectangles, what I'm trying to implement is:
user touch the screen, he could slide between Rectangles. then his finger Lift off, and the last touched rectangle is selected.
(Lift off outside rectangle will trigger nothing)
Just like my lumia 920's keyboard, once you recognized that your finger was in a wrong place, you could slide to the right place, lift off, and the right character show on the screen.
many thanks to you heroes!
That's trickier than it seems, as the MouseLeftButtonUp event will be triggered only if the MouseLeftButtonDown has first been triggered on the control.
I see two ways to achieve this result:
Assign the same MouseLeftButtonDown and MouseLeftButtonUp event handler to all your rectangles. In the MouseLeftButtonDown, call the CaptureMouse method (it tells the control to continue tracking the mouse events even if the cursor isn't on top of the control anymore):
private void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
((UIElement)sender).CaptureMouse();
}
In the MouseLeftButtonDown, release the mouse, then use the VisualTreeHelper.FindElementsInHostCoordinates to find the rectangle on which the cursor was when the even was triggered:
private void MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var element = (UIElement)sender;
element.ReleaseMouseCapture();
var mouseUpRectangle = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(this), this.ContentPanel)
.OfType<Rectangle>()
.FirstOrDefault();
if (mouseUpRectangle != null)
{
Debug.WriteLine("MouseUp in " + mouseUpRectangle.Name);
}
}
(replace ContentPanel by the name of the container in which you've put all your controls)
Not tested but it might work. Subscribe to the MouseLeftButtonUp event of the container in which you've put all your rectangles. Then use the same logic to retrieve the rectangle at the coordinates of the pointer:
private void MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var mouseUpRectangle = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(this), this.ContentPanel)
.OfType<Rectangle>()
.FirstOrDefault();
if (mouseUpRectangle != null)
{
Debug.WriteLine("MouseUp in " + mouseUpRectangle.Name);
}
}
You can find more information in that article I wrote a few months ago.
I'm developing in google maps APIv2. The issue that I'm facing now is I only able to add in one line of word in info windows/snippet. But the output that I want is able to display in break line form as example show below. So is there any possible methods to solve it?
Example:
Coordinate: 03.05085, 101.70506
Speed Limit: 80 km/h
public class MainActivity extends FragmentActivity {
static final LatLng SgBesi = new LatLng(03.05085, 101.76022);
static final LatLng JB = new LatLng(1.48322, 103.69065);
private GoogleMap map;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
map = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
.getMap();
map.addMarker(new MarkerOptions().position(SgBesi)
.title("KM D7.7 Sungai Besi") //name
.snippet("Coordinate: 03.05085, 101.70506")); //description
map.addMarker(new MarkerOptions().position(JB)
.title("test")
.snippet("Hey, how are you?"));
// .icon(BitmapDescriptorFactory //set icon
// .fromResource(R.drawable.ic_launcher)));
// move the camera instantly with a zoom of 15.
map.moveCamera(CameraUpdateFactory.newLatLngZoom(SgBesi, 15));
// zoom in, animating the camera.
map.animateCamera(CameraUpdateFactory.zoomTo(15), 2000, null);
}
}
You need to use a custom InfoWindowAdapter to change the layout of InfoWindow to a custom design defined using a layout file. Basically you need to :
Declare your function using GoogleMap.setInfoWindowAdapter(Yourcustominfowindowadpater)
Have a class like below:
...
class Yourcustominfowindowadpater implements InfoWindowAdapter {
private final View mymarkerview;
Yourcustominfowindowadpater() {
mymarkerview = getLayoutInflater()
.inflate(R.layout.custominfowindow, null);
}
public View getInfoWindow(Marker marker) {
render(marker, mymarkerview);
return mymarkerview;
}
public View getInfoContents(Marker marker) {
return null;
}
private void render(Marker marker, View view) {
// Add the code to set the required values
// for each element in your custominfowindow layout file
}
}
I would like to add something to #tony , According to documentation you cant add action to your custom view as it draw as image in run time.
Note: The info window that is drawn is not a live view. The view is
rendered as an image (using View.draw(Canvas)) at the time it is
returned. This means that any subsequent changes to the view will not
be reflected by the info window on the map. To update the info window
later (for example, after an image has loaded), call showInfoWindow().
Furthermore, the info window will not respect any of the interactivity
typical for a normal view such as touch or gesture events. However you
can listen to a generic click event on the whole info window as
described in the section below.
You will have to use InfoWindowAdapter to create custom info windows. The default implementation somehow removes newlines from snippet, which might be considered a minor bug.
I use a Group to store some Images and draw them to a SpriteBatch. Now I want to detect which Image has been clicked. For this reason I add an InputListener to the Group to get an event on touch down. The incoming InputEvent got an method (getTarget) that returns an reference to the clicked Actor.
If I click on a transparent area of an Actor I want to ignore the incoming event. And if there is an Actor behind it I want to use this instead. I thought about something like this:
myGroup.addListener(new InputListener() {
#Override
public boolean touchDown(InputEvent event, float x, float y, int pointer, int button) {
Actor targetActor = event.getTarget();
// is the touched pixel transparent: return false
// else: use targetActor to handle the event and return true
};
});
Is this the right way to do it? I thought returning false for the method touchDown would continue propagation for the event and let me also receive touchDown events for other Actors at the same position. But this seems to be a misunderstanding...
UPDATE
P.T.s answer solves the problem of getting the right Event. Now I have got the problem to detect if the hit pixel is transparent. It seems to me that I need the Image as a Pixmap to get access. But I do not know how to convert an Image to a Pixmap. I also wonder if this is a good solution in terms of performance and memory usage..
I think you want to override the Actor.hit() method. See the scene2d Hit Detection wiki.
To do this, subclass Image, and put your hit specialization in there. Something like:
public Actor hit(float x, float y, boolean touchable) {
Actor result = super.hit(x, y, touchable);
if (result != null) { // x,y is within bounding box of this Actor
// Test if actor is really hit (do nothing) or it was missed (set result = null)
}
return result;
}
I believe you cannot accomplish this within the touchDown listener because the Stage will have skipped the Actors "behind" this one (only "parent" actors will get the touchDown event if you return false here).