I'm developing an application with the MapView and the GPS.
My problem is that the user might disconnect while using my application, so he'll be walking in a fully grey map. I'd like to cache the map temporarily in order to see it later.
I know that cache the map isn't allowed by the google maps API, but I want to be sure if TEMPORARILY cache it (and delete it when the app is closed) is also forbidden.
I've heard about openstreetmaps / osmdroid, but I'd like to confirm that I HAVE to use it before deleting half of my code.
I know that cache the map isn't allowed by the google maps API, but I want to be sure if TEMPORARILY cache it (and delete it when the app is closed) is also forbidden.
There is no means of accomplishing this with the Google Maps Add-On for Android.
Because I need the satelliteView, I finally used a method that could be improved.
I start a thread that asks the controler to go to a location near the user. It's downloaded and the cache is managed by the MapView:
int latitudeSpan = mapView.getLatitudeSpan();
int longitudeSpan = mapView.getLongitudeSpan();
for(int i=-MEMORIZED_MAP_SIZE;i<=MEMORIZED_MAP_SIZE;i++)
{
for(int j=-MEMORIZED_MAP_SIZE;j<=MEMORIZED_MAP_SIZE;j++)
{
synchronized (this)
{
if(i==j&&j==0) j++;
Message msg = new Message();
msg.arg1=latitude+latitudeSpan*i;
msg.arg2=longitude+longitudeSpan*j;
msg.setTarget(MainActivity.handler);
msg.sendToTarget();
try
{
wait(5000);
}
catch(Exception e){}
}
}
}
Now, I just need to find another way than "wait(5000)" to know that the part of the map I'm looking at is downloaded. I'll modify this message if I find one.
Do you have the whole example of this?
int latitudeSpan = mapView.getLatitudeSpan();
int longitudeSpan = mapView.getLongitudeSpan();
for(int i=-MEMORIZED_MAP_SIZE;i<=MEMORIZED_MAP_SIZE;i++)
{
for(int j=-MEMORIZED_MAP_SIZE;j<=MEMORIZED_MAP_SIZE;j++)
{
synchronized (this)
{
if(i==j&&j==0) j++;
Message msg = new Message();
msg.arg1=latitude+latitudeSpan*i;
msg.arg2=longitude+longitudeSpan*j;
msg.setTarget(MainActivity.handler);
msg.sendToTarget();
try
{
wait(5000);
}
catch(Exception e){}
}
}
}
I managed to solve this partially using 2 MapView Fragments , one of them invisible.
Then a loop was moving the camera to create cache for the other MapView
Related
I want to use the world map in Unity and have been looking at the API of various map services. I need to show a screenshot (a static map with markers) on the screen, and move to the full map view to navigate it after clicking on it.
MapBox managed to display the map with the selected coordinates and add test markers, but that's all I can do with a query like this: https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/url-https%3A%2F%2Fwww.mapbox.com%2Fimg%2Frocket.png(-76.9,38.9)/-76.9,38.9,15/1000x1000?access_token=pk.eyJ1IjoiZGVuZGVhZCIsImEiOiJja2F1dha4egixnnfhmnvtc2u0y3bua2ntin0.GGOyhgN_fEqtPpPc5n6OLg because this request returns a jpg image.
They also have a plugin for Unity, but it's only used in 3d projects and does not allow me to configure the display in 2d.
In MapBox, the mapping I need is implemented using JavaScript for Web and Java for Android. On Android I can do what I need. I can connect to the API on Android, but will I be able to use it in Unity later?
It's the same with Google maps.
Actually, the question is, did someone work with map services in Unity? And how can this be implemented correctly?
I don't know if this is still relevant, but I used Mapbox in Unity (the Mapbox plugin) to create a AR Soundscape by "registering" GameObjects to coordinates and moving them in real-time when the map is moved.
Your problem sounds an awful lot like the one I solved with that.
Basically you provide the Lat/Lon values for your objects and convert them to Unity world space coordinates using the AbstractMap.GeoToWorldPosition() function.
I used a raycast to actually pull that off in-engine, which is quite convenient.
//Edit:
Unity is quite capable of handling 2D projects. You just have to configure it properly and build your project around it.
The following is the class that I use to handle all positioning-related calculations. Maybe it's of some help to you.
namespace TehMightyPotato.Positioning
{
[Serializable]
public class GeoPosition
{
[Tooltip(
"Update frequency of position polling. Update every n-th frame. 1 is every frame, 60 is every 60th frame.")]
[Range(1, 60)]
public int positionUpdateFrequency = 1;
[Tooltip("Should the object have a specified altitude?")]
public bool useYOffset = false;
[Tooltip("If useMeterConversion is activated the yOffsets unit is meters, otherwise its unity units.")]
public bool useMeterConversion = false;
[Tooltip("The actual y position of the object in m or unity units depending on useMeterConversion.")]
public float yOffset = 0;
[Tooltip("X is LAT, Y is LON")]public Vector2d geoVector;
[HideInInspector] public float worldRelativeScale;
// Apply the result of this function to your gameobjects transform.position on every frame to keep them on this position.
public Vector3 GetUnityWorldSpaceCoordinates(AbstractMap map)
{
UpdateWorldRelativeScale(map);
var worldSpaceCoordinates = map.GeoToWorldPosition(geoVector, false);
if (useYOffset)
{
worldSpaceCoordinates.y = yOffset;
}
return worldSpaceCoordinates;
}
public void UpdateWorldRelativeScale(AbstractMap map)
{
worldRelativeScale = map.WorldRelativeScale;
}
public void SetGeoVectorFromRaycast(Vector3 position, AbstractMap map, LayerMask layerMask)
{
var ray = new Ray(position, Vector3.down);
if (Physics.Raycast(ray, out var hitInfo, Mathf.Infinity, layerMask))
{
geoVector = map.WorldToGeoPosition(hitInfo.point);
}
else
{
throw new NullReferenceException("Raycast did not hit the map. Did you turn on map preview?");
}
}
public void SetYOffsetFromRaycast(AbstractMap map, Vector3 position, LayerMask layerMask)
{
UpdateWorldRelativeScale(map);
// using raycast because of possible y-non-zero maps/ terrain etc.
var ray = new Ray(position, Vector3.down);
if (Physics.Raycast(ray, out var hitInfo, Mathf.Infinity, layerMask))
{
var worldSpaceDistance = Vector3.Distance(position, hitInfo.point);
if (useMeterConversion)
{
yOffset = worldSpaceDistance * worldRelativeScale;
}
else
{
yOffset = worldSpaceDistance;
}
}
else
{
throw new NullReferenceException("Could not find map below. Is map preview turned on?");
}
}
}
}
After struggling a few hours on making my app detect this QRCode:
I realized that the problem was the in the QRCode appearance. After inverting the colors, the detection was working perfectly..
Is there a way to make Vision API detect the first QRCode? I tried to enable all symbologies but it did not work. I guess it is possible because the app QR Code Reader detects it.
I improved googles example app "barcode-reader" to detect both inverted colored barcodes and regular ones.
here is a link to googles example app:
https://github.com/googlesamples/android-vision/tree/master/visionSamples/barcode-reader
I did so by editing "CameraSource" class,
package: "com.google.android.gms.samples.vision.barcodereader.ui.camera".
I added a parameter: private boolean isInverted = false;
and changed function void setNextFrame(byte[] data, Camera camera):
void setNextFrame(byte[] data, Camera camera) {
synchronized (mLock) {
if (mPendingFrameData != null) {
camera.addCallbackBuffer(mPendingFrameData.array());
mPendingFrameData = null;
}
if (!mBytesToByteBuffer.containsKey(data)) {
Log.d(TAG,
"Skipping frame. Could not find ByteBuffer associated with the image " +
"data from the camera.");
return;
}
mPendingTimeMillis = SystemClock.elapsedRealtime() - mStartTimeMillis;
mPendingFrameId++;
if (!isInverted){
for (int y = 0; y < data.length; y++) {
data[y] = (byte) ~data[y];
}
isInverted = true;
} else {
isInverted = false;
}
mPendingFrameData = mBytesToByteBuffer.get(data);
// Notify the processor thread if it is waiting on the next frame (see below).
mLock.notifyAll();
}
}
I think this is still an open issue, please see link for details. One workaround for this as stated by a developer:
Right, the barcode API generally doesn't support color-inverted codes. There's no parameter or option to control this at the moment. Though some APIs support them, I don't believe it's a common feature.
For a workaround, you could preprocess the colors in the bitmap before passing them to the barcode API (perhaps inverting colors on alternate frames).
Hope this helps.
My goal is to create an Android app which download a map from ArcGIS portal when connected to internet, then use them offline. I would like to use service pattern, so later the app can have synchronization feature. I followed a tutorial from ArcGIS here.
I am currently stuck at downloading the map part. I expect the downloaded map is in mobile map package (.mmpk), but instead my download directory have a package.info file, and a folder of geodatabase and .mmap files as image shown here. Based on my understanding, I should have an .mmpk file to use them offline.
Following the tutorial steps, I am able to (1) create an offline map task, (2) specify the parameters, and (3) examine the offline capabilities. However in step (4) generate and download the offline map, I expect the downloaded map will be in mobile map package (.mmpk) but its not; as i mentioned above with image shown. In step (5) open and use the offline map, i am able to view offline map when using mobile map package (.mmpk) file that i transfer manually into the device. I also tried to open and use my downloaded (.mmap) file but no map showed up.
My full code by steps is shown below:
(1) create an offline map task
// Load map from a portal item
final Portal portal = new Portal("http://www.arcgis.com");
final PortalItem webmapItem = new PortalItem(portal, "acc027394bc84c2fb04d1ed317aac674");
// Create map and add it to the view
myMap = new ArcGISMap(webmapItem);
mMapView = (MapView) findViewById(R.id.mapView);
mMapView.setMap(myMap);
// Create task and set parameters
final OfflineMapTask offlineMapTask = new OfflineMapTask(myMap);
(2) specify the parameters
// Create default parameters
final ListenableFuture<GenerateOfflineMapParameters> parametersFuture = offlineMapTask.createDefaultGenerateOfflineMapParametersAsync(areaOfInterest);
parametersFuture.addDoneListener(new Runnable() {
#Override
public void run() {
try {
final GenerateOfflineMapParameters parameters = parametersFuture.get();
// Update the parameters if needed
// Limit maximum scale to 5000 but take all the scales above (use 0 as a MinScale)
parameters.setMaxScale(5000);
parameters.setIncludeBasemap(false);
// Set attachment options
parameters.setAttachmentSyncDirection(GenerateGeodatabaseParameters.AttachmentSyncDirection.UPLOAD);
parameters.setReturnLayerAttachmentOption(GenerateOfflineMapParameters.ReturnLayerAttachmentOption.EDITABLE_LAYERS);
// Request the table schema only (existing features won't be included)
parameters.setReturnSchemaOnlyForEditableLayers(true);
// Update the title to contain the region
parameters.getItemInfo().setTitle(parameters.getItemInfo().getTitle() + " (Central)");
// Create new item info
final OfflineMapItemInfo itemInfo = new OfflineMapItemInfo();
// Override thumbnail with the new image based on the extent
final ListenableFuture<Bitmap> exportImageFuture = mMapView.exportImageAsync();
exportImageFuture.addDoneListener(new Runnable() {
#Override
public void run() {
try {
Bitmap mapImage = exportImageFuture.get();
// Scale to thumbnail size
Bitmap thumbnailImage = Bitmap.createScaledBitmap(mapImage, 200, 133, false);
// Convert to byte[]
ByteArrayOutputStream stream = new ByteArrayOutputStream();
thumbnailImage.compress(Bitmap.CompressFormat.JPEG, 50, stream);
byte[] thumbnailBytes = stream.toByteArray();
stream.close();
// Set values to the itemInfo
itemInfo.setThumbnailData(thumbnailBytes);
itemInfo.setTitle("Water network (Central)");
itemInfo.setSnippet(webmapItem.getSnippet()); // Copy from the source map
itemInfo.setDescription(webmapItem.getDescription()); // Copy from the source map
itemInfo.setAccessInformation(webmapItem.getAccessInformation()); // Copy from the source map
itemInfo.getTags().add("Water network");
itemInfo.getTags().add("Data validation");
// Set metadata to parameters
parameters.setItemInfo(itemInfo);
} catch (Exception e) {
e.printStackTrace();
}
}
});
(3) examine the offline capabilities
final ListenableFuture<OfflineMapCapabilities> offlineMapCapabilitiesFuture =
offlineMapTask.getOfflineMapCapabilitiesAsync(parameters);
offlineMapCapabilitiesFuture.addDoneListener(new Runnable() {
#Override
public void run() {
try {
OfflineMapCapabilities offlineMapCapabilities = offlineMapCapabilitiesFuture.get();
if (offlineMapCapabilities.hasErrors()) {
// Handle possible errors with layers
for (java.util.Map.Entry<Layer, OfflineCapability> layerCapability :
offlineMapCapabilities.getLayerCapabilities().entrySet()) {
if (!layerCapability.getValue().isSupportsOffline()) {
showMessage(layerCapability.getKey().getName() + " cannot be taken offline.");
showMessage("Error : " + layerCapability.getValue().getError().getMessage());
}
}
// Handle possible errors with tables
for (java.util.Map.Entry<FeatureTable, OfflineCapability> tableCapability :
offlineMapCapabilities.getTableCapabilities().entrySet()) {
if (!tableCapability.getValue().isSupportsOffline()) {
showMessage(tableCapability.getKey().getTableName() + " cannot be taken offline.");
showMessage("Error : " + tableCapability.getValue().getError().getMessage());
}
}
} else {
// All layers and tables can be taken offline!
showMessage("All layers are good to go!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
(4) generate and download the offline map
String mExportPath = String.valueOf(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)) + File.separator + "New";
showMessage(mExportPath);
// Create and start a job to generate the offline map
final GenerateOfflineMapJob generateOfflineJob =
offlineMapTask.generateOfflineMap(parameters, mExportPath);
// Show that job started
final ProgressBar progressBarOffline = (ProgressBar) findViewById(R.id.progressBarOffline);
progressBarOffline.setVisibility(View.VISIBLE);
generateOfflineJob.start();
generateOfflineJob.addJobDoneListener(new Runnable() {
#Override
public void run() {
// Generate the offline map and download it
GenerateOfflineMapResult result = generateOfflineJob.getResult();
if (!result.hasErrors()) {
showMessage("no error");
mobileMapPackage = result.getMobileMapPackage();
// Job is finished and all content was generated
showMessage("Map " + mobileMapPackage.getItem().getTitle() +
" saved to " + mobileMapPackage.getPath());
// Show offline map in a MapView
mMapView.setMap(result.getOfflineMap());
// Show that job completed
progressBarOffline.setVisibility(View.INVISIBLE);
} else {
showMessage("error");
// Job is finished but some of the layers/tables had errors
if (result.getLayerErrors().size() > 0) {
for (java.util.Map.Entry<Layer, ArcGISRuntimeException> layerError : result.getLayerErrors().entrySet()) {
showMessage("Error occurred when taking " + layerError.getKey().getName() + " offline.");
showMessage("Error : " + layerError.getValue().getMessage());
}
}
if (result.getTableErrors().size() > 0) {
for (java.util.Map.Entry<FeatureTable, ArcGISRuntimeException> tableError : result.getTableErrors().entrySet()) {
showMessage("Error occurred when taking " + tableError.getKey().getTableName() + " offline.");
showMessage("Error : " + tableError.getValue().getMessage());
}
}
// Show that job completed
progressBarOffline.setVisibility(View.INVISIBLE);
}
}
});
(5) open and use the offline map
// Create the mobile map package
final MobileMapPackage mapPackage = new MobileMapPackage(mobileMapPackage.getPath());
// Load the mobile map package asynchronously
mapPackage.loadAsync();
// Add done listener which will invoke when mobile map package has loaded
mapPackage.addDoneLoadingListener(new Runnable() {
#Override
public void run() {
// Check load status and that the mobile map package has maps
if(mapPackage.getLoadStatus() == LoadStatus.LOADED && mapPackage.getMaps().size() > 0){
// Cdd the map from the mobile map package to the MapView
mMapView.setMap(mapPackage.getMaps().get(0));
}else{
// Log an issue if the mobile map package fails to load
showMessage(mapPackage.getLoadError().getMessage());
}
}
});
showMessage() in my code is showing Toast.
public void showMessage(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
I worry if my .mmpk expectation is wrong, or my step goes wrong somewhere because I still not fully understand the whole process. This is my first time working with ArcGIS map in Android. I could not find much sample code to experiment, so really appreciate someone who could help.
Thank you!
The task created an exploded mobile map package, which works just the same as a .mmpk file. Open it like this:
final MobileMapPackage mapPackage =
new MobileMapPackage("/data/com.geoinfo.asmasyakirah.arcgis/files/Documents/New");
(If you can't access it there, you might want to generate the mobile map package in Environment.getExternalStorageDirectory() instead of Environment.DIRECTORY_DOCUMENTS.)
According to the documentation for the MobileMapPackage constructor:
Creates a new MobileMapPackage from the .mmpk file or exploded mobile map package at the given path.
If you really must have it as a .mmpk file, simply zip it using an Android API for making zip files and name it .mmpk instead of .zip.
Kinda late on the topic but i had several days working on this and found out something that may help some of you :
I created my mapData via this class : https://github.com/Esri/arcgis-runtime-samples-java/blob/master/src/main/java/com/esri/samples/map/generate_offline_map/GenerateOfflineMapSample.java
As you can see it creates a folder containing package.info + p13 (in which you find geodatabase file + mmap file)
WHen i tried offline to load this data, no errors appeared but the layer was empty and i could just see the carroying.
In fact after much more tries, i had to check that besides geodatabase and mmap file i could find a .tpk file (TilePackaged)
This one was never available (somehow due to network issues during the online download) and nothing alerted me.
Now that this tpk file is there, all items are clearly displayed like 'water network'
TL;DR; : check that tpk file is donwloaded during the online preparation.
I'm trying out the Google API V2 Heatmap (Android) which was introduced this february and it's not working entirely. I'm not sure if it's the same problem as Google Maps API v2 HeatMap Won't Reliably Display (and I can't contact him by comments because I don't have enough rep.. :c ).
Using locations from file
Anyway, what I was trying to do was an App with just a GoogleMap and the same locations as in their guide (https://developers.google.com/maps/documentation/android/utility/heatmap) and when I launch it, I can only see 3/5 locations.
I tried adding more points and still, it's consistently not showing them all, but I can't see a pattern or something. The code is the same as in their guide.
private void addHeatMap() {
List<LatLng> list = null;
// Get the data: latitude/longitude positions of police stations.
try {
list = readItems(R.raw.police_stations);
} catch (JSONException e) {
Toast.makeText(this, "Problem reading list of locations.", Toast.LENGTH_LONG).show();
}
// Create a heat map tile provider, passing it the latlngs of the police stations.
mProvider = new HeatmapTileProvider.Builder()
.data(list)
.build();
// Add a tile overlay to the map, using the heat map tile provider.
mOverlay = mMap.addTileOverlay(new TileOverlayOptions().tileProvider(mProvider));
}
private ArrayList<LatLng> readItems(int resource) throws JSONException {
ArrayList<LatLng> list = new ArrayList<LatLng>();
InputStream inputStream = getResources().openRawResource(resource);
String json = new Scanner(inputStream).useDelimiter("\\A").next();
JSONArray array = new JSONArray(json);
for (int i = 0; i < array.length(); i++) {
JSONObject object = array.getJSONObject(i);
double lat = object.getDouble("lat");
double lng = object.getDouble("lng");
LatLng latLng = new LatLng(lat, lng);
list.add(latLng);
}
return list;
}
Trying with weighted locations had the same result, some of them showing and some not.
Using locations "onClick"
In this case, normal LatLng were added and the map was updated until I got to the equator and then it stopped :p.
For the case of weightedLatLngs, it didn't update/show any :(
That's basically it... I don't know where to look for more information, whether it's something with the API, the device or something else.
Does anyone know why this might happen and/or where to look for a solution ?
Thank you in advance !
Btw, I'm using Android 4.0.4 on a Samsung Galaxy S Duos if that may prove useful..
The Googlemaps API for Heatmap only allows for 1,000 plotted points. You can reduced that by filtering out your original data or by converting to "weighted locations".
This looks like it was caused by this bug, where bounds calculations were excluding max values.
This is now fixed.
I followed this example to parse a local GPX file in Android:
http://android-coding.blogspot.pt/2013/01/get-latitude-and-longitude-from-gpx-file.html
All works fine to access "lat" and "long" but I need also to get the "ele" value but all my tentatives were unsuccessful.
Anyone can give me some hits to do that?
Thanks in advance!
Best regards,
NR.
I will add my library for GPX parsing to these answers: https://github.com/ticofab/android-gpx-parser. It provides two ways to parse you GPX file: once you obtain / create a GPXParser object (mParser in the examples below), you can then either parse directly your GPX file
Gpx parsedGpx = null;
try {
InputStream in = getAssets().open("test.gpx");
parsedGpx = mParser.parse(in);
} catch (IOException | XmlPullParserException e) {
e.printStackTrace();
}
if (parsedGpx == null) {
// error parsing track
} else {
// do something with the parsed track
}
or you can parse a remote file:
mParser.parse("http://myserver.com/track.gpx", new GpxFetchedAndParsed() {
#Override
public void onGpxFetchedAndParsed(Gpx gpx) {
if (gpx == null) {
// error parsing track
} else {
// do something with the parsed track
}
}
});
Contributions are welcome.
you have the "Node node = nodelist_trkpt.item(i);" in your first loop.
Get the child elements from this node an run through these child elements.
e.g.:
NodeList nList = node.getChildNodes();
for(int j=0; j<nList.getLength(); j++) {
Node el = nList.item(j);
if(el.getNodeName().equals("ele")) {
System.out.println(el.getTextContent());
}
}
Update: I've added parsing "ele" element as well, so this code could match your requirements.
I will propose different approach: https://gist.github.com/kamituel/6465125.
In my approach I don't create an ArrayList of all track points (this is done in the example you posted). Such a list can consume quite a lot of memory, which can be an issue on Android.
I've even given up on using regex parsing to avoid allocating too many objects (which causes garbage collector to run).
As a result, running Java with 16Mb heap size, parsing GPX file with over 600 points, garbage collector will be run only 12 times. I'm sure one could go lower, but I didn't optimize it heavily yet.
Usage:
GpxParser parser = new GpxParser(new FileInputStream(file));
TrkPt point = null;
while ((point = parser.nextTrkPt()) != null) {
// point.getLat()
// point.getLon()
}
I've successfully used this code to parse around 100 Mb of GPX files on Android. Sorry it's not in the regular repo, I didn't plan to share it just yet.
I've ported the library GPXParser by ghitabot to Android.
https://github.com/urizev/j4gpx