I have an Arcgis map with pins.When i tap on a pin i am showing a callout(popover)over the pins which works perfectly fine.But when i zoom in/out the map, callout does't position itself with respect to the pin on the map.How can i always show callout on top of pin while zooming in/out.
tap on pin and callout pops up
and the image where pop up moves away from pin when zoom in
Note: I have made changes to the existing sample project of Arcgis map app i.e. SymbolizingResults
Here are the changes i have made to the SymbolizingResults Activity
public class SymbolizingResults extends Activity {
MapView map;
Button queryBtn;
GraphicsLayer gl;
Callout callout;
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
map = (MapView) findViewById(R.id.map);
map.addLayer(new ArcGISTiledMapServiceLayer(
"http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer"));
gl = new GraphicsLayer();
gl.setRenderer(createClassBreaksRenderer());
Point p = new Point(37.6922222, -97.3372222);
HashMap<String, Object> map1 = new HashMap<String, Object>();
map1.put("NAME", "India");
PictureMarkerSymbol pic = new PictureMarkerSymbol(this,getResources().getDrawable(R.drawable.pin_dest));
Graphic gr = new Graphic(p,pic,map1);
gl.addGraphic(gr);
map.addLayer(gl);
queryBtn = (Button) findViewById(R.id.querybtn);
queryBtn.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// Sets query parameter
Query query = new Query();
query.setWhere("STATE_NAME='Kansas'");
query.setReturnGeometry(true);
String[] outfields = new String[] { "NAME", "STATE_NAME",
"POP07_SQMI" };
query.setOutFields(outfields);
query.setOutSpatialReference(map.getSpatialReference());
Query[] queryParams = { query };
AsyncQueryTask qt = new AsyncQueryTask();
qt.execute(queryParams);
}
});
// Sets 'OnSingleTapListener' so that when single tap
// happens, Callout would show 'SQMI' information associated
// with tapped 'Graphic'
map.setOnSingleTapListener(new OnSingleTapListener() {
private static final long serialVersionUID = 1L;
public void onSingleTap(float x, float y) {
if (!map.isLoaded())
return;
Point toDroppedPinPoint = map.toMapPoint(x, y);
System.out.println("X : "+toDroppedPinPoint.getX());
System.out.println("Y : "+toDroppedPinPoint.getY());
int[] uids = gl.getGraphicIDs(x, y, 2);
if (uids != null && uids.length > 0) {
int targetId = uids[0];
Graphic gr = gl.getGraphic(targetId);
callout = map.getCallout();
// Sets Callout style
callout.setStyle(R.xml.countypop);
/* String countyName = (String) gr.getAttributeValue("NAME");
String countyPop = gr.getAttributeValue("POP07_SQMI")
.toString();*/
// Sets custom content view to Callout
callout.setContent(loadView("Anshul", "India"));
callout.show(map.toMapPoint(new Point(x, y)));
} else {
if (callout != null && callout.isShowing()) {
callout.hide();
}
}
}
});
}
// Creates custom content view with 'Graphic' attributes
private View loadView(String countyName, String pop07) {
View view = LayoutInflater.from(CalloutSampleActivity.this).inflate(
R.layout.sqmi, null);
final TextView name = (TextView) view.findViewById(R.id.county_name);
name.setText(countyName + "'s SQMI");
final TextView number = (TextView) view.findViewById(R.id.pop_sqmi);
number.setText(pop07);
final ImageView photo = (ImageView) view
.findViewById(R.id.family_photo);
photo.setImageDrawable(CalloutSampleActivity.this.getResources()
.getDrawable(R.drawable.family));
return view;
}`
The trouble is this line:
callout.show(map.toMapPoint(new Point(x, y)));
Here you're saying you want to show the callout at the point that the user tapped. That's what the sample does, and in the sample it always makes sense because the sample's graphics are all polygons (i.e. counties in Kansas).
But for a point, like the pin you added, you don't want to show the callout at the tapped point. If the tapped point is a couple of pixels away from the pin, and then you zoom out, the difference can be hundreds of miles! Instead, you want to show the callout at the pin graphic's point. But you only want to do that if it's actually a point, so you need to check the graphic's geometry with instanceof.
I replaced the above line with this and it works:
Geometry graphicGeom = gr.getGeometry();
if (graphicGeom instanceof Point) {
callout.show((Point) graphicGeom);
} else {
callout.show(toDroppedPinPoint);
}
I couldn't see the code about how you are zooming in & out. But logically you should update the callout position in zoom-in & zoom-out too like you are doing in onSingleTap().
Related
EXAMPLE IMAGE
I put many circles on the map, and I wish to these circles have their own OncircleClick Listener
but the problem is, I can regist only one circle click listener on the googleMap, and This Listener is shared by every circle, so every Times I click any circles, same listener event would occured.
Could you let me know how to make each circles can have their own On circle click Listener? thank you.
here is my tried code :
private void drawCircle(LatLng point, boolean isOverThreshold, final Bundle results){
GoogleMap.OnCircleClickListener onCircleClickListener = new GoogleMap.OnCircleClickListener() {
#Override
public void onCircleClick(Circle circle) {
int sixty_one = results.getInt("60hz_1");
int eighty_one = results.getInt("180hz_1");
int sixty_two = results.getInt("60hz_2");
int eighty_two = results.getInt("180hz_2");
int sixty_three = results.getInt("60hz_3");
int eighty_three = results.getInt("180hz_3");
double speed = results.getDouble("speed");
Toast.makeText(ReplayActivity.this, "CH1: ("+sixty_one+","+eighty_one+") \n"+" CH2: (" + sixty_two+","+eighty_two+") \n" + "CH3: ("+sixty_three+","+eighty_three+")\n speed:"+speed+"m/s )",Toast.LENGTH_SHORT).show();
Log.d("gd","circle clicked!");
}
};
// Instantiating CircleOptions to draw a circle around the marker
CircleOptions circleOptions = new CircleOptions();
// Specifying the center of the circle
circleOptions.center(point);
(....)
// Adding the circle to the GoogleMap
map.addCircle(circleOptions);
map.setOnCircleClickListener(onCircleClickListener);
//but this oncircleListener is shared by every circle click event..
}
You can use Circle.setTag() method of created circle object to set, for example the ID of Circle object (or more complex structure) as Tag, and then use Circle.getTag() inside yours onCircleClickListener to get this ID (or more complex structure). Something like that:
private void drawCircle(LatLng point, boolean isOverThreshold, final Bundle results){
GoogleMap.OnCircleClickListener onCircleClickListener = new GoogleMap.OnCircleClickListener() {
#Override
public void onCircleClick(Circle circle) {
// get stored Tag object from passed circle object
YOUR_TAG_OBJECT_CLASS yourTagObjectName = (YOUR_TAG_OBJECT_CLASS)circle.getTag();
if (yourTagObjectName != null) {
// parse it
switch (yourTagObjectName.id) {
case ID_1: ... break;
case ID_2: ... break;
...
}
}
int sixty_one = results.getInt("60hz_1");
int eighty_one = results.getInt("180hz_1");
int sixty_two = results.getInt("60hz_2");
int eighty_two = results.getInt("180hz_2");
int sixty_three = results.getInt("60hz_3");
int eighty_three = results.getInt("180hz_3");
double speed = results.getDouble("speed");
Toast.makeText(ReplayActivity.this, "CH1: ("+sixty_one+","+eighty_one+") \n"+" CH2: (" + sixty_two+","+eighty_two+") \n" + "CH3: ("+sixty_three+","+eighty_three+")\n speed:"+speed+"m/s )",Toast.LENGTH_SHORT).show();
Log.d("gd","circle clicked!");
}
};
// Instantiating CircleOptions to draw a circle around the marker
CircleOptions circleOptions = new CircleOptions();
// Specifying the center of the circle
circleOptions.center(point);
(....)
// Adding the circle to the GoogleMap
Circle circle = map.addCircle(circleOptions); // <- store circle object
circle.setTag(YOUR_TAG_ID_OR_OBJECT); // <- set Tag to stored circle object
map.setOnCircleClickListener(onCircleClickListener);
}
I have two tables in my game. When I tap on one the cells of the first table, I want to draw an arrow that follows my finger so that I can move the arrow head to a table cell from the second table. I need to be able to know which was the initial cell and which was the final cell (where the arrow started and where the arrow finished). If the user takes the finger off the screen and it wasn't on a table cell I want nothing to happen.
ArrayList<Actor> myActor = new ArrayList<Actor>();
ArrayList<Actor> anotherActor = new ArrayList<Actor>();
// fill array with my actors
Table firstTable = new Table();
for (int i = 0; i < 7; i++) {
firstTable.add(myActor.get(i));
myActor.get(i).addListener(new ActorGestureListener() {
public void touchDown(InputEvent event, float x, float y, int pointer, int button) {
// draw arrow that follows finger
}
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
// if event finished and was on a cell from the
// second table get starting actor and finishing actor
// else nothing happens
}
});
}
Table secondTable = new Table();
for (int i = 0; i < 7; i++) {
secondTable.add(anotherActor.get(i));
}
How should I do this?
Hope this helps.
Actor startFromActor = null; // Variable outside of method
public void foo() {
// smt before
ArrayList<Actor> myActor = new ArrayList<Actor>();
ArrayList<Actor> anotherActor = new ArrayList<Actor>();
// fill array with my actors
Table firstTable = new Table();
Table secondTable = new Table();
for (int i = 0; i < 7; i++) {
final Actor curActor = myActor.get(i);
firstTable.add(curActor);
curActor.addListener(new ActorGestureListener() {
public void touchDown(InputEvent event, float x, float y, int pointer, int button) {
if (pointer > 0) return false; // For only one finger
startFromActor = curActor;
// draw arrow that follows finger
}
});
}
for (int i = 0; i < 7; i++) {
final Actor curActor = anotherActor.get(i);
secondTable.add(curActor);
curActor.addListener(new ActorGestureListener() {
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
if (pointer > 0) return false; // For only one finger
if (startFromActor != null) {
// do something
// startActor - actor from first table
// curActor - actor from second table
startFromActor = null; // do not forget to null-setting
}
}
});
}
}
If you need both directions from table to table, you should set touchUp and touchDown events for every Actor and then check who is owner for start and end actors;
You can draw an arrow using the built in Mesh utilities in libgdx:
Build a Mesh at init
It is suggested to only build one mesh ( usually at init ) and then re-use ( render ) it during runtime as many times as many arrows the application requires. For the urposes of building the mesh, the Meshbuilder and ArrowShapeBuilder can be used. Example:
MeshBuilder meshbuilder = new MeshBuilder();
meshbuilder.begin(VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal, GL20.GL_TRIANGLES);
ArrowShapeBuilder.build(
meshbuilder,
0,0,0 /* Starting xyz */
5,5,0 /* Ending xyz */
0.1f, /* percentage of arrow head */
0.1f, /* percentage of stem thickness */
10 /* divisions, basically level of detail */
);
Mesh debug_arrow = meshbuilder.end();
By the above a renderable mesh is constructed.
Render the arrow to your app
This step is quite simple, provided a Batch is already available :
debug_arrow.render(batch.getShader(), GL20.GL_TRIANGLES);
The position of the arrow is then being set by the stage projection matrix. A Batch should be available, the very least the rendering stage has one through getBatch().
Note: The mesh parameter musn't always be GL20.GL_TRIANGLES, but the parameters must match in meshbuilder.begin and debug_arrow.render.
I'm trying to show a ground overlay with markers on it to my users. I'm trying to restrain the view to only this image placed on the map. I want the user to only see the image as a ground overlay placed on the map and not be able to go to the surrounding map. And if they go over the edge, the gestures will be blocked.
I want something like this:
I don't want this:
show-only-ground-overlays-map-android or this:
I tried to set my map.setLatLngBoundsForCameraTarget() to my image bounds but the result is something like the previous image...
The next thing I tried is to set a bunch of LatLng objects all around the ground overlay and check with curScreen.contains(customMapDetectionPoints.get(LatLng Object)) to see if they appear on the viewport. It's does work but I can't stop the camera to go over the edge...
Here my test code so far :
private GroundOverlay groundOverlay;
private GoogleMap globalMap;
private final int DETECTION_POINTS_CUSTOM_MAP = 20;
private List<LatLng> customMapDetectionPoints = new ArrayList<>();
//Fully Working as suppose to
#Override
public void onMapReady(GoogleMap map) {
//Other Stuff...
LatLngBounds mapBounds = groundOverlay.getBounds();
map.setLatLngBoundsForCameraTarget(mapBounds);
globalMap = map;
LatLng northwest = new LatLng( mapBounds.northeast.latitude, mapBounds.southwest.longitude);
LatLng northeast = mapBounds.northeast;
LatLng southeast = new LatLng( mapBounds.southwest.latitude, mapBounds.northeast.longitude);
LatLng southwest = mapBounds.southwest;
//My ground overlay is rectangle so I don't need to follow a path or something like that
setDetectionPoints(northwest, southwest);
setDetectionPoints(northeast, southeast);
setDetectionPoints(northwest, northeast);
setDetectionPoints(southwest, southeast);
map.setOnCameraMoveStartedListener(new GoogleMap.OnCameraMoveStartedListener() {
#Override
public void onCameraMoveStarted(int i) {
LatLngBounds curScreen = globalMap.getProjection().getVisibleRegion().latLngBounds;
CameraPosition cameraPosition = globalMap.getCameraPosition();
for (int x =0;x<customMapDetectionPoints.size();x++) {
if (curScreen.contains(customMapDetectionPoints.get(x))) {
cancelMapMovement(cameraPosition);
Log.d("OUT", "Ground Overlay is outside viewport");
return;
} else {
globalMap.getUiSettings().setAllGesturesEnabled(true);
Log.d("IN", "Ground Overlay is inside viewport");
}
}
}
});
//Add 20 new location between two location
//Fully Working as suppose to
public void setDetectionPoints(LatLng fromPos, LatLng toPos) {
double pointLatitude = fromPos.latitude;
double pointLongitude = fromPos.longitude;
double addingValue;
if (fromPos.latitude == toPos.latitude) {
addingValue = (toPos.longitude - fromPos.longitude)/DETECTION_POINTS_CUSTOM_MAP;
for (int i = 0; i < DETECTION_POINTS_CUSTOM_MAP; i++) {
pointLongitude += addingValue;
LatLng pointsPos = new LatLng(pointLatitude, pointLongitude);
customMapDetectionPoints.add(pointsPos);
}
} else if (fromPos.longitude == toPos.longitude) {
addingValue = (toPos.latitude - fromPos.latitude)/DETECTION_POINTS_CUSTOM_MAP;
for (int i = 0; i < DETECTION_POINTS_CUSTOM_MAP; i++) {
pointLatitude += addingValue;
LatLng pointsPos = new LatLng(pointLatitude, pointLongitude);
customMapDetectionPoints.add(pointsPos);
}
}
}
//The problem is here!
public void cancelMapMovement(CameraPosition camPos ) {
//HOW CAN I STOP THE MOVEMENT OVER THE GROUND OVERLAY EDGE
//And make sure that the user dosen't see over the edge
globalMap.getUiSettings().setAllGesturesEnabled(false);
globalMap.moveCamera(CameraUpdateFactory.newCameraPosition(camPos));
}
At this point I think I have two possible solutions:
1- Only use the setLatLngBoundsForCameraTarget() function and set a offset or margin to the camera. But is it possible and will it work for my use case ?
2- Solve my camera restriction problem with the code already written
Thanks for helping! Suggest other solution if you find one!
I will provide more info if wanted.
I am using Android Graph View to display line graphs. I need to change the horizontal labels to have nice rounded numbers. In the screen shot attached the values are
1
11:15
22:29
33:44
I need to adjust the position of the horizontal labels and the vertical lines that come off of it so the labels read
0
11:00
23:00
33:44
The code for the above screen shot uses this base class
protected TextView text;
protected GraphView graph;
#Override
public View onCreateView(final LayoutInflater inflater,
ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.ride_chart_fragment, null, false);
graph = (GraphView) view.findViewById(R.id.graph);
if (graph==null){
Log.e("Rides","Graph should not ne null");
return view;
}
Series series = linePoints();
if (series==null) return view;
graph.getGridLabelRenderer().setNumHorizontalLabels(4);
graph.addSeries(series);
setViewport(graph, series);
text = (TextView)view.findViewById(R.id.textView);
text.setVisibility(View.INVISIBLE);
graph.getGridLabelRenderer().setLabelFormatter(new DefaultLabelFormatter() {
#Override
public String formatLabel(double value, boolean isValueX) {
if (isValueX) {
// show normal x values
return UnitConverter.secondsToInterval((float) value);
} else {
return super.formatLabel(value, isValueX) + units(new Settings(inflater.getContext()));
}
}
});
graph.getGridLabelRenderer().setLabelVerticalWidth((int)getResources().getDimension(R.dimen.graph_label_vertical_width));
graph.getGridLabelRenderer().setLabelHorizontalHeight((int)getResources().getDimension(R.dimen.graph_label_vertical_height));
graph.getGridLabelRenderer().setGridColor(Color.parseColor("#20000000"));
graph.getGridLabelRenderer().setGridStyle(GridLabelRenderer.GridStyle.BOTH);
//graph.getGridLabelRenderer().
return view;
}
and this child class to render the graph
protected Series linePoints() {
//RideDetailActivity activity = (RideDetailActivity)getActivity();
if (activity==null || activity.isFinishing() || activity.ride==null) return null;
Settings settings = new Settings(activity);
long rideId = activity.ride.sqlRide.getId();
LezyneLinkApplication application = (LezyneLinkApplication)activity.getApplicationContext();
DaoSession session = application.getDaoSession();
RideElevationDao table = session.getRideElevationDao();
List<RideElevation> elevations = table
.queryBuilder()
.where(RideElevationDao.Properties.RideId.eq(rideId))
.orderAsc(RideElevationDao.Properties.Index)
.list();
DataPoint dataPoints[] = new DataPoint[elevations.size()];
int index=0;
boolean isMetric = settings.isMetric();
for (RideElevation elevation : elevations){
double y = elevation.getY();
if (!settings.isMetric()){
y = UnitConverter.convertMetersToFeet(y);
}
DataPoint point = new DataPoint(elevation.getX(),y);
dataPoints[index] = point;
index++;
}
LineGraphSeries series = new LineGraphSeries<DataPoint>(dataPoints);
series.setDrawBackground(true);
series.setColor(Color.argb(0xFF, 0x8e, 0x00, 0xe8));
series.setBackgroundColor(Color.argb(0x3F, 0x47, 0x2c, 0x17));
series.setThickness(6);
return series;
}
#Override
protected void setViewport(GraphView graph,Series series) {
graph.getViewport().setXAxisBoundsManual(true);
graph.getViewport().setMaxX(series.getHighestValueX());
graph.getViewport().setYAxisBoundsManual(true);
double lowest = series.getLowestValueY();
double highest = series.getHighestValueY();
graph.getViewport().setMinY(lowest);
Settings settings = new Settings(context);
if (settings.isMetric()){
if (highest<lowest+121) highest = lowest+121;
}
else {
if (highest<lowest+400) highest = lowest+400;
}
graph.getViewport().setMaxY(highest);
}
Im not seeing any way of doing this with the library as is so I am considering changing the source code. Did I miss something in the API? Anybody have any suggestions about where in the code to add this functionality.
you have two ways:
1) (difficult) with dynamic viewport take a look into the source code and find the point where the humanRound is done (GridLabelRenderer.java)
understand it, and modify ;)
2) use a fixed viewport and calculate on your own the min and max bounds and you can change the numberOfHorizontalLabels to get the best match.
This code snippet is taken from here:
protected void drawOverlayBitmap(Canvas canvas, Point drawPosition, Projection projection,
byte drawZoomLevel) {
synchronized (this.visibleItems) {
// erase the list of visible items
this.visibleItems.clear();
this.numberOfItems = size();
if (this.numberOfItems < 1) {
// no items to draw
return;
}
// draw the overlay items
for (int itemIndex = 0; itemIndex < this.numberOfItems; ++itemIndex) {
// get the current item
this.overlayItem = createItem(itemIndex);
synchronized (this.overlayItem) {
// check if the item has a position
if (this.overlayItem.getPoint() == null) {
continue;
}
// make sure that the cached item position is valid
if (drawZoomLevel != this.overlayItem.cachedZoomLevel) {
this.overlayItem.cachedMapPosition = projection.toPoint(
this.overlayItem.getPoint(),
this.overlayItem.cachedMapPosition, drawZoomLevel);
this.overlayItem.cachedZoomLevel = drawZoomLevel;
}
// calculate the relative item position on the display
this.itemPosition.x = this.overlayItem.cachedMapPosition.x - drawPosition.x;
this.itemPosition.y = this.overlayItem.cachedMapPosition.y - drawPosition.y;
// get the correct marker for the item
if (this.overlayItem.getMarker() == null) {
if (this.defaultMarker == null) {
// no marker to draw the item
continue;
}
this.itemMarker = this.defaultMarker;
} else {
this.itemMarker = this.overlayItem.getMarker();
}
// get the position of the marker
this.markerBounds = this.itemMarker.copyBounds();
// calculate the bounding box of the marker
this.left = this.itemPosition.x + this.markerBounds.left;
this.right = this.itemPosition.x + this.markerBounds.right;
this.top = this.itemPosition.y + this.markerBounds.top;
this.bottom = this.itemPosition.y + this.markerBounds.bottom;
// check if the bounding box of the marker intersects with the canvas
if (this.right >= 0 && this.left <= canvas.getWidth() && this.bottom >= 0
&& this.top <= canvas.getHeight()) {
// set the position of the marker
this.itemMarker.setBounds(this.left, this.top, this.right, this.bottom);
// draw the item marker on the canvas
this.itemMarker.draw(canvas);
// restore the position of the marker
this.itemMarker.setBounds(this.markerBounds);
// add the current item index to the list of visible items
this.visibleItems.add(Integer.valueOf(itemIndex));
}
}
}
}
}
now what that i find hard to get is why did they synchronized overlayItem, it makes no sense to me, there are several reasons why i think its redundent, on top of those is the fact that within the sync block, there is a refrence to a final member of the OverlayItem class which is the GeoPoint, so what sense does it make to think other threads could change this?!
thanks!
An object market final marks an object as a constant, for example having:
final ArrayList<Integer> visibleItems;
As a result you are not allowed to assign another object to the visibleItems, because visibleItems is marked final:
For example trying to do like in the code below, will result in a compilation error:
ArrayList<Integer> anotherArray = new ArrayList<Integer>();
visibleItems = anotherArray; //Compile error.
However, this does not mean that all the elements the array list contains, are also marked as final. No.
One thread may add new elementes, onother may remove some elements:
anotherArray.add(2);
anotherArray.add(5);
//........
anotherArray.remove(5);
That is why the variable is kept syncronized, to assure no more than 1 thread can update the content of the array list simulaniously.