I'm trying to use Google Maps with custom tiles. For this, I use the API demo's from Google:
https://github.com/googlemaps/android-samples
and as an example I used TileOverlayDemoActivity as a base. Everything works as expected, but when you zoom in/out and the zoomlevel changes, all the tiles disappear and are being build up again, resulting in a gray screen of about one or two seconds. When you later-on zoom to this level again, the issue is not there, but, if you restart the app, it is there again!
Here is a recording I made from the API Demo's app (this is as-is, I have not changed anything) : https://www.youtube.com/watch?v=OZ3aqLOZ2CY
First I thought the downloading of the tiles took some time, so I tested with using a lightweight TileProvider:
public class MyTileProvider implements TileProvider {
#Override
public Tile getTile(int i, int i1, int i2) {
return new Tile(256, 256, byteArray);
}
}
where byteArray is always the same, created from a `Bitmap once:
ByteArrayOutputStream stream = new ByteArrayOutputStream();
b.compress(Bitmap.CompressFormat.PNG, 100, stream);
byteArray = stream.toByteArray();
But here, still the same issue.
I don't think there is a solution to this issue, as I haven't found any on the web.
But if someone has a workaround, I could accept that. I am thinking of trying to zoom in from top to bottom for each level so they are drawn once (as I don't see this issue after a zoomlevel has been drawn)
I have a workaround for this and answered it on a different question here
Related
I'm using the nativescript-google-maps-sdk plugin to create a Google map.
Everything works fine but I've got a problem with my custom marker icons, if you look at these pictures you can see that the icon size is not preserved on Android, making them very, very small to the point where you can barely even see them. This happens both in the emulators and on a real phone.
On IOS however the size is fine, as you can see in the 2nd image. The icon images have a size of 16x16 pixels and are in .png format.
I haven't been able to find any solution to this so this is my last resort, does anyone know why this might be happening?
This is the code I use to create the markers:
getImage(this.getWarningIcon(warning.status)).then((result) => {
const icon = new Image();
icon.imageSource = result;
const marker = new Marker();
marker.position = warning.centerOfPolygon;
marker.icon = icon;
marker.flat = true;
marker.anchor = [0.5, 0.5];
marker.visible = warning.isVisible;
marker.zIndex = zIndexOffset;
marker.infoWindowTemplate = 'markerTemplate';
marker.userData = {
description: warning.description,
startTime: warning.startTime,
completionTime: warning.completionTime,
freeText: warning.freeText
};
this.layers.push(marker);
this.map.addMarker(marker);
});
In that case 16px sounds too low for a high density device. Increase the size of the image sent from server or locally resize the image before passing it to marker.
You may also consider generating a scaled bitmap natively if you are familiar with Android apis. Image processing is something always complicated in Android. Using drawables are recommend when your images are static at least.
Sorry I am new to Android development.
Wondering if there is any method to load Open Cycle Map using OSMdroid please?
From the website, seems there is no easy way to do so:
https://github.com/osmdroid/osmdroid/wiki/Map-Sources
Therefore, would any one can give me some tips how to do so please?
What I can think the only way is to define Tile Source manually as below.
Wondering if there is any easier way to do so please?
final String[] tileURLs = {"http://a.tile.thunderforest.com/cycle/",
"http://b.tile.thunderforest.com/cycle/",
"http://c.tile.thunderforest.com/cycle/"};
final ITileSource OCM =
new XYTileSource("Open Cycle Map",
0,
19,
512,
".png",
tileUrls,
"from open cycle map");
Thanks a lot
Defining a tile-source is a correct way how to do it. And it's a perfectly fine way, many build-in tile-sources are defined in the same way.
However, according to the documentation at the http://thunderforest.com/maps/opencyclemap/ you should obtain and use API key:
Want to use these tiles? The generic tile format string for the
OpenCycleMap layer is:
https://tile.thunderforest.com/cycle/{z}/{x}/{y}.png?apikey=<insert-your-apikey-here>
Therefore you should include you API key:
final ITileSource OCM =
new XYTileSource("Open Cycle Map",
0,
19,
512,
".png?apikey=<insert-your-apikey-here>",
tileUrls,
"from open cycle map");
(This is just modified code from the question. I didn't test it and therefore some parameters don't have to be correct)
After some weeks of waiting I finally have my Project Tango. My idea is to create an app that generates a point cloud of my room and exports this to .xyz data. I'll then use the .xyz file to show the point cloud in a browser! I started off by compiling and adjusting the point cloud example that's on Google's github.
Right now I use the onXyzIjAvailable(TangoXyzIjData tangoXyzIjData) to get a frame of x y and z values; the points. I then save these frames in a PCLManager in the form of Vector3. After I'm done scanning my room, I simple write all the Vector3 from the PCLManager to a .xyz file using:
OutputStream os = new FileOutputStream(file);
size = pointCloud.size();
for (int i = 0; i < size; i++) {
String row = String.valueOf(pointCloud.get(i).x) + " "
+ String.valueOf(pointCloud.get(i).y) + " "
+ String.valueOf(pointCloud.get(i).z) + "\r\n";
os.write(row.getBytes());
}
os.close();
Everything works fine, not compilation errors or crashes. The only thing that seems to be going wrong is the rotation or translation of the points in the cloud. When I view the point cloud everything is messed up; the area I scanned is not recognizable, though the amount of points is the same as recorded.
Could this have to do something with the fact that I don't use PoseData together with the XyzIjData? I'm kind of new to this subject and have a hard time understanding what the PoseData exactly does. Could someone explain it to me and help me fix my point cloud?
Yes, you have to use TangoPoseData.
I guess you are using TangoXyzIjData correctly; but the data you get this way is relative to where the device is and how the device is tilted when you take the shot.
Here's how i solved this:
I started from java_point_to_point_example. In this example they get the coords of 2 different points with 2 different coordinate system and then write those coordinates wrt the base Coordinate frame pair.
First of all you have to setup your exstrinsics, so you'll be able to perform all the transformations you'll need. To do that I call mExstrinsics = setupExtrinsics(mTango) function at the end of my setTangoListener() function. Here's the code (that you can find also in the example I linked above).
private DeviceExtrinsics setupExtrinsics(Tango mTango) {
//camera to IMU tranform
TangoCoordinateFramePair framePair = new TangoCoordinateFramePair();
framePair.baseFrame = TangoPoseData.COORDINATE_FRAME_IMU;
framePair.targetFrame = TangoPoseData.COORDINATE_FRAME_CAMERA_COLOR;
TangoPoseData imu_T_rgb = mTango.getPoseAtTime(0.0,framePair);
//IMU to device transform
framePair.targetFrame = TangoPoseData.COORDINATE_FRAME_DEVICE;
TangoPoseData imu_T_device = mTango.getPoseAtTime(0.0,framePair);
//IMU to depth transform
framePair.targetFrame = TangoPoseData.COORDINATE_FRAME_CAMERA_DEPTH;
TangoPoseData imu_T_depth = mTango.getPoseAtTime(0.0,framePair);
return new DeviceExtrinsics(imu_T_device,imu_T_rgb,imu_T_depth);
}
Then when you get the point Cloud you have to "normalize" it. Using your exstrinsics is pretty simple:
public ArrayList<Vector3> normalize(TangoXyzIjData cloud, TangoPoseData cameraPose, DeviceExtrinsics extrinsics) {
ArrayList<Vector3> normalizedCloud = new ArrayList<>();
TangoPoseData camera_T_imu = ScenePoseCalculator.matrixToTangoPose(extrinsics.getDeviceTDepthCamera());
while (cloud.xyz.hasRemaining()) {
Vector3 rotatedV = ScenePoseCalculator.getPointInEngineFrame(
new Vector3(cloud.xyz.get(),cloud.xyz.get(),cloud.xyz.get()),
camera_T_imu,
cameraPose
);
normalizedCloud.add(rotatedV);
}
return normalizedCloud;
}
This should be enough, now you have a point cloud wrt you base frame of reference.
If you overimpose two or more of this "normalized" cloud you can get the 3D representation of your room.
There is another way to do this with rotation matrix, explained here.
My solution is pretty slow (it takes around 700ms to the dev kit to normalize a cloud of ~3000 points), so it is not suitable for a real time application for 3D reconstruction.
Atm i'm trying to use Tango 3D Reconstruction Library in C using NDK and JNI. The library is well documented but it is very painful to set up your environment and start using JNI. (I'm stuck at the moment in fact).
Drifting
There still is a problem when I turn around with the device. It seems that the point cloud spreads out a lot.
I guess you are experiencing some drifting.
Drifting happens when you use Motion Tracking alone: it consist of a lot of very small error in estimating your Pose that all together cause a big error in your pose relative to the world. For instance if you take your tango device and you walk in a circle tracking your TangoPoseData and then you draw you trajectory in a spreadsheet or whatever you want you'll notice that the Tablet will never return at his starting point because he is drifting away.
Solution to that is using Area Learning.
If you have no clear ideas about this topic i'll suggest watching this talk from Google I/O 2016. It will cover lots of point and give you a nice introduction.
Using area learning is quite simple.
You have just to change your base frame of reference in TangoPoseData.COORDINATE_FRAME_AREA_DESCRIPTION. In this way you tell your Tango to estimate his pose not wrt on where it was when you launched the app but wrt some fixed point in the area.
Here's my code:
private static final ArrayList<TangoCoordinateFramePair> FRAME_PAIRS =
new ArrayList<TangoCoordinateFramePair>();
{
FRAME_PAIRS.add(new TangoCoordinateFramePair(
TangoPoseData.COORDINATE_FRAME_AREA_DESCRIPTION,
TangoPoseData.COORDINATE_FRAME_DEVICE
));
}
Now you can use this FRAME_PAIRS as usual.
Then you have to modify your TangoConfig in order to issue Tango to use Area Learning using the key TangoConfig.KEY_BOOLEAN_DRIFT_CORRECTION. Remember that when using TangoConfig.KEY_BOOLEAN_DRIFT_CORRECTION you CAN'T use learningmode and load ADF (area description file).
So you cant use:
TangoConfig.KEY_BOOLEAN_LEARNINGMODE
TangoConfig.KEY_STRING_AREADESCRIPTION
Here's how I initialize TangoConfig in my app:
TangoConfig config = tango.getConfig(TangoConfig.CONFIG_TYPE_DEFAULT);
//Turning depth sensor on.
config.putBoolean(TangoConfig.KEY_BOOLEAN_DEPTH, true);
//Turning motiontracking on.
config.putBoolean(TangoConfig.KEY_BOOLEAN_MOTIONTRACKING,true);
//If tango gets stuck he tries to autorecover himself.
config.putBoolean(TangoConfig.KEY_BOOLEAN_AUTORECOVERY,true);
//Tango tries to store and remember places and rooms,
//this is used to reduce drifting.
config.putBoolean(TangoConfig.KEY_BOOLEAN_DRIFT_CORRECTION,true);
//Turns the color camera on.
config.putBoolean(TangoConfig.KEY_BOOLEAN_COLORCAMERA, true);
Using this technique you'll get rid of those spreads.
PS
In the Talk i linked above, at around 22:35 they show you how to port your application to Area Learning. In their example they use TangoConfig.KEY_BOOLEAN_ENABLE_DRIFT_CORRECTION. This key does not exist anymore (at least in Java API). Use TangoConfig.KEY_BOOLEAN_DRIFT_CORRECTION instead.
I have Google Maps SDK running with custom markers on an android app. Everything works fine on our test phones -- an A7000, an Samsung and other. However, when I run the app on Nexus 5 or LG devices, one type of custom marker displaying white on the map.
While the markers are showing white,will perform desired behavior.
We're super confused about this, especially because another custom marker (that is very similar) works fine. Other than hardware, the only other difference between our test phones and the Nexus 5 is that the Nexus is running Android 6.0, vs 5.x and 4.x for our test phones.
Code using to add marker
for(i=0;i<latLngs.size;i++)
{
LatLng latLng=latLngs.get(i);
Marker m = mMap.addMarker(new MarkerOptions().position(latLng).title("Title").icon(BitmapDescriptorFactory.fromResource(R.drawable.marker)));
}
for change the marker icon using
m.setIcon(BitmapDescriptorFactory.fromResource(R.drawable.marker1));
On nexus device google map appear like below, some off the markers become white ,i am using loop to add markers.
EDIT: With version 9.2.56 of the Google Play Services app released on June 13th, the bug has been fixed.
I found a workaround to your problem, however it is quite ugly and it's probably not advised to use it unless you know you're going to use a little number of markers.
Instead of this:
//Getting a reference to your activity's resources
final Resources resources = myActivity.getResources();
//Defining your drawable res id
final int resId = R.drawable.my_drawable_res_id;
marker.setIcon(BitmapDescriptorFactory.fromResource(resId));
Do this:
//Getting a reference to your activity's resources
final Resources resources = myActivity.getResources();
//Defining your drawable res id
final int resId = R.drawable.my_drawable_res_id;
marker.setIcon(
BitmapDescriptorFactory.fromBitmap(
BitmapFactory.decodeResource(resources, resId)));
Workaround found after reading this link (thanks #antonio in your comments):
https://code.google.com/p/gmaps-api-issues/issues/detail?id=9765
The bug is caused on certain devices with a recent version of the Google Play Services library (probably 8.7+ or 9+).
It happens if you share a BitmapDescriptor with multiple markers, so the workaround is to re-create one every time. I think BitmapDescriptorFactory.fromResource might cache read resources somehow, that's why you need to decode it as a bitmap.
Here is the quote of the #10 reply of transbao at the link antonio gave:
We can repro this bug which causes certain marker icons to render as
white patches. Your app may be affected if an icon bitmap is shared
among multiple markers, although the issue only manifests in specific
usage scenarios.
In the short term, we'd recommend the workaround in #8 -- use a unique
Bitmap object for each marker:
marker.setIcon(BitmapDescriptorFactory.fromBitmap(
BitmapFactory.decodeResource(getResources(),
R.drawable.drawableid)));
and
new MarkerOptions().icon(BitmapDescriptorFactory.fromBitmap(
BitmapFactory.decodeResource(getResources(),
R.drawable.drawableid)));
Creating a BitmapDescriptor once and reusing it won’t be sufficient.
E.g. if you’re doing:
BitmapDescriptor bd = ...; marker1.setIcon(bd); marker2.setIcon(bd);
...then the workaround would be:
marker1.setIcon(BitmapDescriptorFactory.fromBitmap(
BitmapFactory.decodeResource(getResources(),
R.drawable.drawableid)));
marker2.setIcon(BitmapDescriptorFactory.fromBitmap(
BitmapFactory.decodeResource(getResources(),
R.drawable.drawableid)));
Please note that, if your app uses a lot of markers, this workaround
could possibly result in higher memory consumption. Also,
unfortunately the workaround doesn’t apply when the default icon is
used via BitmapDescriptorFactory.defaultMarker().
I have some dynamic tile content to display on top of a map (specifically, weather images -- radar, satellite, temperatures, etc.). I'm using Google Maps API for Android v2.
The problem I'm having is that apparently the only way to update the tile images (i.e. when new data arrives, or when the frame advances in a time lapse animation) is to call TileOverlay.clearImageCache. Unfortunately, when I do that, the tile overlay flickers off for a moment. This is because clearImageCache immediately removes the existing tile images from the display, but there's a delay before it can decode and display new tile images.
I'm using a custom TileProvider that caches the tile images, rather than fetching them from the server each time. But even when it's only feeding cached tiles (i.e. there's no significant delay imposed by my TileProvider.getTile implementation), there's still enough of a delay in the process that the user can see a flicker.
Does anyone know of a way to avoid this flicker? Is there some way I can double-buffer the tile overlay? I tried to double-buffer it with two TileOverlays attached to the map, one of which is invisible. But the invisible TileOverlay does not start fetching any tiles from my TileProvider -- even after I call clearImageCache.
Would it be possible to load the upcoming tile image however have the visibility set to false? Tile.setVisible(false);
Then when you want to change (after the upcoming tile has loaded), set the upcoming tile to be visible and the current tile to be invisible?
CurrentTile.setVisible(false);
NewTile.setVisible(true);
This way the change all occurs within the same render frame, and there is no delay waiting for cached images to load.
A fairly good solution would be to separate tile downloading and caching from TileProvider. This way you can fully control when they are downloaded and only replace byte[] references after downloading all.
This might be a bit more complex, because you have to take care of the current visible region and zoom not to download them all, but only those that will be visible.
Edit
Tested with the following code:
try {
InputStream is = getAssets().open("tile1.jpg");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = is.read();
while (b != -1) {
baos.write(b);
b = is.read();
}
byte[] array = baos.toByteArray();
cache = new Tile(256, 256, array);
is = getAssets().open("tile2.jpg");
baos = new ByteArrayOutputStream();
b = is.read();
while (b != -1) {
baos.write(b);
b = is.read();
}
array = baos.toByteArray();
nextCache = new Tile(256, 256, array);
} catch (IOException ex) {
Log.e("tag", "error reading tiles", ex);
}
tileOverlay = map.addTileOverlay(new TileOverlayOptions().tileProvider(new TileProvider() {
#Override
public Tile getTile(int x, int y, int zoom) {
return cache;
}
}));
and somewhere later:
Tile temp = cache;
cache = nextCache;
nextCache = temp;
tileOverlay.clearTileCache();
"Fastest" possible code still fails.
If you cannot switch to GroundOverlay or Markers, another idea is trying to use third party map tiles, your current weather tiles above and next tiles below, so they can load and switch them (using zOrder) after few seconds.
The solution I found for this is to make the layer have a transparency of 1. This makes them hidden but still requesting tiles. You can then switch which layers have a transparency of 1 and 0.
There is still possibility of some flicker due to having a render call in between the changing of the transparency of the two layers. There is no way to make this an atomic operation.
Also found that there's not a great way to know when the layer has fully loaded the tile. Even after you return the tile from the TileProvider, Google Maps has to do some processing on it, so you need some delay before switching tiles.
The solution that worked for me (I tried refreshing tiles every second):
// Adding "invisible" overlay
val newTileOverlay = mMap?.addTileOverlay(
TileOverlayOptions()
.tileProvider(getTileProvider()).transparency(1f).visible(true)
)
mTileOverlay?.transparency = 0.5f // making previous overlay visible
mOldTileOverlay?.remove() // removing previously displayed visible overlay
mOldTileOverlay = mTileOverlay
mTileOverlay = newTileOverlay
So we have two layers at a time (one visible and one invisible). I'm not sure how it influences the performance.