Skip to content

Commit

Permalink
Hillshading: Android scoped storage (mapsforge#1338)
Browse files Browse the repository at this point in the history
  • Loading branch information
usrusr authored and devemux86 committed Jul 18, 2022
1 parent 0cba2d2 commit 43ade4a
Show file tree
Hide file tree
Showing 14 changed files with 725 additions and 178 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2022 usrusr
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mapsforge.map.android.hills;

import android.content.ContentResolver;
import org.mapsforge.core.util.IOUtils;
import org.mapsforge.map.layer.hills.DemFile;
import org.mapsforge.map.layer.hills.DemFileFS;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;

public class DemFileAndroidContent implements DemFile {
private final DemFolderAndroidContent.Entry entry;
private final ContentResolver contentResolver;

public DemFileAndroidContent(DemFolderAndroidContent.Entry entry, ContentResolver contentResolver) {
this.entry = entry;
this.contentResolver = contentResolver;
}

@Override
public String getName() {
return entry.name;
}

@Override
public InputStream openInputStream() throws FileNotFoundException {
return contentResolver.openInputStream(entry.uri);
}

@Override
public long getSize() {
return entry.size;
}

@Override
public ByteBuffer asByteBuffer() throws IOException {
InputStream stream = null;
try {
String nameLowerCase = entry.name.toLowerCase();
stream = contentResolver.openInputStream(entry.uri);
if (nameLowerCase.endsWith(".zip")) {
return DemFileFS.tryZippedSingleHgt(entry.name, stream);
} else {
return DemFileFS.streamAsByteBuffer(entry.name, stream, (int) entry.size);
}
} finally {
IOUtils.closeQuietly(stream);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* Copyright 2022 usrusr
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mapsforge.map.android.hills;

import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.DocumentsContract;
import org.mapsforge.core.util.IOUtils;
import org.mapsforge.map.layer.hills.DemFile;
import org.mapsforge.map.layer.hills.DemFolder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

public class DemFolderAndroidContent implements DemFolder {

final private Uri dirUri;
private final Context context;
final private ContentResolver contentResolver;

public DemFolderAndroidContent(Uri dirUri, Context context, ContentResolver contentResolver) {
this.dirUri = dirUri;
this.context = context;
this.contentResolver = contentResolver;
}

private List<Entry> children = null;

@Override
public Iterable<DemFolder> subs() {
if (children == null) children = queryNested();
return new TransformedIt<DemFolder>(children) {
@Override
boolean accept(Entry entry) {
return entry.isDir;
}

@Override
DemFolder transform(Entry entry) {
return new DemFolderAndroidContent(entry.uri, context, contentResolver);
}
};
}

@Override
public Iterable<DemFile> files() {
if (children == null) children = queryNested();
return new TransformedIt<DemFile>(children) {
@Override
boolean accept(Entry entry) {
return !entry.isDir;
}

@Override
DemFileAndroidContent transform(Entry entry) {
return new DemFileAndroidContent(entry, contentResolver);
}
};
}

abstract class TransformedIt<T> implements Iterable<T> {
protected TransformedIt(Iterable<Entry> source) {
this.source = source;
}

abstract T transform(Entry entry);

abstract boolean accept(Entry entry);

final Iterable<Entry> source;

/**
* basically a guava Iterators transform(filter( implemented with lookahead
*/
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
final Iterator<Entry> it = source.iterator();
Entry next;
boolean endReached = false;

{
returnCurrentSetNext();
}

Entry returnCurrentSetNext() {
Entry cur = next;
while (true) {
if (!it.hasNext()) {
next = null;
endReached = true;
break;
} else {
next = it.next();
if (accept(next)) {
break;
}
}
}
return cur;
}

@Override
public boolean hasNext() {
return !endReached;
}

@Override
public T next() {
return transform(returnCurrentSetNext());
}

@Override
public void remove() {
it.remove();
}
};
}
}

static class Entry {
final Uri uri;
final String name;
final boolean isDir;
final long size;

private Entry(Uri uri, String name, boolean isDir, long size) {
this.uri = uri;
this.name = name;
this.isDir = isDir;
this.size = size;
}

@Override
public String toString() {
return (isDir ? "Dir" : "File") + "{" +
"name='" + name + '\'' +
"uri=" + uri +
'}';
}
}

private List<Entry> queryNested() {
if (dirUri == null) {
return Collections.emptyList();
}

String dirDocId = DocumentsContract.getTreeDocumentId(dirUri);
if (DocumentsContract.isDocumentUri(context, dirUri)) {
dirDocId = DocumentsContract.getDocumentId(dirUri);
}

Uri parentUri = DocumentsContract.buildDocumentUriUsingTree(dirUri, dirDocId);
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(parentUri, dirDocId);


String[] columns = new String[]{
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE,
DocumentsContract.Document.COLUMN_SIZE,
};

Cursor c = null;
try {
c = contentResolver.query(childrenUri, columns, null, null, null);

List<Entry> result = new ArrayList<>();
while (c.moveToNext()) {
String fileDocId = c.getString(0);
String name = c.getString(1);
String mimeType = c.getString(2);
long size = c.getLong(3);

Uri uri = DocumentsContract.buildDocumentUriUsingTree(dirUri, fileDocId);
boolean isDir = DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
result.add(new Entry(uri, name, isDir, size));
}

return result;
} finally {
IOUtils.closeQuietly(c);
}
}

@Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (!(obj instanceof DemFolderAndroidContent)) {
return false;
}
return dirUri.equals(((DemFolderAndroidContent) obj).dirUri);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020 usrusr
* Copyright 2020-2022 usrusr
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
Expand All @@ -14,17 +14,10 @@
*/
package org.mapsforge.map.layer.hills;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public abstract class AbsShadingAlgorithmDefaults implements ShadingAlgorithm {

Expand All @@ -36,63 +29,14 @@ public abstract class AbsShadingAlgorithmDefaults implements ShadingAlgorithm {
public RawShadingResult transformToByteBuffer(HgtCache.HgtFileInfo source, int padding) {
int axisLength = getAxisLenght(source);
int rowLen = axisLength + 1;
InputStream stream = null;
FileChannel channel = null;
try {
ByteBuffer map = null;
File file = source.getFile();
String nameLowerCase = file.getName().toLowerCase();
if (nameLowerCase.endsWith(".zip")) {

ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(file));
ZipEntry entry;
String expectedNameLC = nameLowerCase.substring(0, nameLowerCase.length() - 4) + ".hgt";
while (null != (entry = zipInputStream.getNextEntry())) {
if (!expectedNameLC.equals(entry.getName().toLowerCase())) continue;

map = ByteBuffer.allocate((int) entry.getSize());
int todo = (int) entry.getSize();
int done = 0;
while (todo > 0) {
int read = zipInputStream.read(map.array(), done, todo);
if (read == 0) {
LOGGER.log(Level.SEVERE, "failed to read entire .hgt in " + file.getName() + " " + done + " of " + todo + " done");
return null;
}
done += read;
todo -= read;
}
map.order(ByteOrder.BIG_ENDIAN);
break;
}
if (map == null) {
LOGGER.log(Level.SEVERE, "no matching .hgt in " + file.getName());
return null;
}
} else {
FileInputStream fileInputStream = new FileInputStream(file);
channel = fileInputStream.getChannel();
stream = fileInputStream;
map = channel.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
map.order(ByteOrder.BIG_ENDIAN);
}
ByteBuffer map = source.getFile().asByteBuffer();

byte[] bytes = convert(map, axisLength, rowLen, padding, source);
return new RawShadingResult(bytes, axisLength, axisLength, padding);
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
return null;
} finally {
if (channel != null) try {
channel.close();
} catch (IOException e) {
e.printStackTrace();
}
if (stream != null) try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2022 usrusr
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mapsforge.map.layer.hills;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;

public interface DemFile {
String getName();

InputStream openInputStream() throws FileNotFoundException;

long getSize();

ByteBuffer asByteBuffer() throws IOException;
}
Loading

0 comments on commit 43ade4a

Please sign in to comment.