-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
aqoleg
committed
Nov 5, 2019
0 parents
commit 0ba5263
Showing
59 changed files
with
3,013 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
# cat | ||
|
||
All maps offline in one app and track writer. | ||
|
||
[download](https://github.com/aqoleg/cat/releases/download/4.0.0/cat.apk) | ||
|
||
Put tiles in the /cat/maps/mapName/z/y/x.png or /x.jpeg | ||
and/or specifiy parameters in /cat/maps/mapName/properties.txt as json file: | ||
- "name" - optional, name of the map | ||
- "url" - optional, url to download as java formatted string "https://example/x=%1$d/y=%2$d/z=%3$d" | ||
- "size" - optional, size of the tile | ||
- "projection": "ellipsoid" - optional, for ellipsoid projection |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
apply plugin: 'com.android.application' | ||
|
||
android { | ||
compileSdkVersion 26 | ||
buildToolsVersion "27.0.1" | ||
defaultConfig { | ||
applicationId "space.aqoleg.cat" | ||
minSdkVersion 11 | ||
targetSdkVersion 26 | ||
versionCode 46 | ||
versionName "4.0.0" | ||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" | ||
} | ||
buildTypes { | ||
release { | ||
minifyEnabled true | ||
shrinkResources true | ||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | ||
} | ||
} | ||
} | ||
|
||
dependencies { | ||
compile fileTree(include: ['*.jar'], dir: 'libs') | ||
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { | ||
exclude group: 'com.android.support', module: 'support-annotations' | ||
}) | ||
testCompile 'junit:junit:4.12' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="space.aqoleg.cat"> | ||
<uses-feature android:name="android.hardware.location.gps" /> | ||
<uses-feature android:name="android.hardware.location.network" /> | ||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> | ||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> | ||
<uses-permission android:name="android.permission.INTERNET" /> | ||
<application android:label="@string/label" android:icon="@mipmap/icon" android:description="@string/description" | ||
android:allowBackup="false"> | ||
<activity android:name=".MainActivity" android:launchMode="singleTask" | ||
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"> | ||
<intent-filter> | ||
<action android:name="android.intent.action.MAIN" /> | ||
<category android:name="android.intent.category.LAUNCHER" /> | ||
</intent-filter> | ||
<intent-filter> | ||
<action android:name="android.intent.action.VIEW" /> | ||
<category android:name="android.intent.category.DEFAULT" /> | ||
<category android:name="android.intent.category.BROWSABLE" /> | ||
<data android:scheme="geo" /> | ||
</intent-filter> | ||
</activity> | ||
<service android:name=".MainService" android:description="@string/serviceDescription" /> | ||
</application> | ||
</manifest> |
199 changes: 199 additions & 0 deletions
199
src/app/src/main/java/space/aqoleg/cat/BitmapCache.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
/* | ||
cache with bitmaps | ||
when get the tile, returns bitmap from cache and puts on the top of the stack, | ||
or, if there is no such tile, asynchronously loads it from memory and puts it on the top of the stack | ||
instead of the bottom item, if there is no such tile in the memory, try to get it from the lower zooms | ||
and calls downloader in the one instance | ||
*/ | ||
package space.aqoleg.cat; | ||
|
||
import android.graphics.Bitmap; | ||
import android.os.AsyncTask; | ||
|
||
import java.io.RandomAccessFile; | ||
import java.util.Arrays; | ||
|
||
class BitmapCache implements Downloader.Callback { | ||
private final Maps maps; | ||
private final Callback callback; | ||
|
||
private final int cacheSize; | ||
private final int[] cacheMapN; | ||
private final int[] cacheZ; | ||
private final int[] cacheY; | ||
private final int[] cacheX; | ||
private final Bitmap[] cacheBitmap; | ||
private final boolean[] cacheToDownload; | ||
private final int[] cacheStackLevel; | ||
private int nextStackLevel = 0; // possibly can not overflow | ||
|
||
private boolean hasLoader = false; | ||
private boolean hasDownloader = false; | ||
|
||
BitmapCache(Callback callback) { | ||
maps = Maps.getInstance(); | ||
this.callback = callback; | ||
cacheSize = getCacheSize(); | ||
cacheMapN = new int[cacheSize]; | ||
Arrays.fill(cacheMapN, -1); // tile with mapN = 0, z = 0, y = 0, x = 0 did not loaded yet | ||
cacheZ = new int[cacheSize]; | ||
cacheY = new int[cacheSize]; | ||
cacheX = new int[cacheSize]; | ||
cacheBitmap = new Bitmap[cacheSize]; | ||
cacheToDownload = new boolean[cacheSize]; | ||
cacheStackLevel = new int[cacheSize]; | ||
} | ||
|
||
@Override | ||
public void onDownloadFinish(int mapN, int z, int y, int x, Bitmap bitmap) { | ||
for (int i = 0; i < cacheSize; i++) { | ||
if (x == cacheX[i] && y == cacheY[i] && z == cacheZ[i] && mapN == cacheMapN[i]) { | ||
if (bitmap != null) { | ||
cacheBitmap[i] = bitmap; | ||
} | ||
cacheToDownload[i] = false; // even if this tile did not loaded, do not try one more time | ||
break; | ||
} | ||
} | ||
hasDownloader = false; | ||
callback.onBitmapCacheLoad(); | ||
} | ||
|
||
// returns bitmap from cache or null if it does not exist in the cache | ||
Bitmap getBitmap(int mapN, int z, int y, int x) { | ||
for (int i = 0; i < cacheSize; i++) { | ||
if (x == cacheX[i] && y == cacheY[i] && z == cacheZ[i] && mapN == cacheMapN[i]) { | ||
// put on the top of the stack | ||
nextStackLevel++; | ||
cacheStackLevel[i] = nextStackLevel; | ||
// start download | ||
if (cacheToDownload[i] && !hasDownloader && maps.canDownload(mapN)) { | ||
hasDownloader = true; | ||
new Downloader(mapN, z, y, x, this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||
} | ||
return cacheBitmap[i]; | ||
} | ||
} | ||
// if tile did not find in the cache, start loader | ||
if (!hasLoader) { | ||
hasLoader = true; | ||
new Loader(mapN, z, y, x).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | ||
} | ||
return null; | ||
} | ||
|
||
private int getCacheSize() { | ||
try { | ||
RandomAccessFile reader = new RandomAccessFile("/proc/meminfo", "r"); | ||
String line = reader.readLine(); | ||
reader.close(); | ||
// MemTotal: 1030428 kB | ||
line = line.substring(line.indexOf("MemTotal:") + 9, line.indexOf("kB")).trim(); | ||
int value = Integer.valueOf(line); | ||
// 30 + ~50 tiles per gb | ||
value = value / 20000; | ||
if (value < 10) { | ||
value = 10; | ||
} else if (value > 200) { | ||
value = 200; | ||
} | ||
return value + 30; | ||
} catch (Exception exception) { | ||
Data.getInstance().writeLog("can not determine memory size: " + exception.toString()); | ||
} | ||
return 50; | ||
} | ||
|
||
interface Callback { | ||
void onBitmapCacheLoad(); | ||
} | ||
|
||
class Loader extends AsyncTask<Void, Void, Void> { | ||
private final int loaderMapN; | ||
private final int loaderZ; | ||
private final int loaderY; | ||
private final int loaderX; | ||
private Bitmap bitmap; | ||
private boolean isBitmapLoaded; | ||
|
||
Loader(int mapN, int z, int y, int x) { | ||
loaderMapN = mapN; | ||
loaderZ = z; | ||
loaderY = y; | ||
loaderX = x; | ||
} | ||
|
||
@Override | ||
protected Void doInBackground(Void... voids) { | ||
bitmap = maps.getTile(loaderMapN, loaderZ, loaderY, loaderX); | ||
isBitmapLoaded = bitmap != null; | ||
if (!isBitmapLoaded) { | ||
// try to fill with tile from previous zoom | ||
int fullTileSize = maps.getSize(loaderMapN); | ||
int xLeftPx = 0; | ||
int yTopPx = 0; | ||
int size = fullTileSize; | ||
int z = loaderZ; | ||
int y = loaderY; | ||
int x = loaderX; | ||
for (int i = 0; i < 5; i++) { | ||
z--; | ||
if (z == 0) { | ||
break; | ||
} | ||
size = size >> 1; | ||
|
||
xLeftPx = xLeftPx >> 1; | ||
if ((x & 0b1) == 1) { | ||
// right half of the tile | ||
xLeftPx += fullTileSize >> 1; | ||
} | ||
x = x >> 1; | ||
|
||
yTopPx = yTopPx >> 1; | ||
if ((y & 0b1) == 1) { | ||
// bottom half of the tile | ||
yTopPx += fullTileSize >> 1; | ||
} | ||
y = y >> 1; | ||
|
||
bitmap = maps.getTile(loaderMapN, z, y, x); | ||
if (bitmap != null) { | ||
bitmap = Bitmap.createBitmap(bitmap, xLeftPx, yTopPx, size, size); | ||
bitmap = Bitmap.createScaledBitmap(bitmap, fullTileSize, fullTileSize, true); | ||
return null; | ||
} | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
@Override | ||
protected void onPostExecute(Void aVoid) { | ||
super.onPostExecute(aVoid); | ||
// do operation in the stack in the main thread | ||
// search for the bottom item of the stack | ||
int cacheN = 0; | ||
int lowestStackLevel = Integer.MAX_VALUE; | ||
for (int i = 0; i < cacheSize; i++) { | ||
if (cacheStackLevel[i] < lowestStackLevel) { | ||
cacheN = i; | ||
lowestStackLevel = cacheStackLevel[i]; | ||
} | ||
} | ||
// put tile instead the bottom item | ||
cacheMapN[cacheN] = loaderMapN; | ||
cacheZ[cacheN] = loaderZ; | ||
cacheY[cacheN] = loaderY; | ||
cacheX[cacheN] = loaderX; | ||
cacheBitmap[cacheN] = bitmap; | ||
cacheToDownload[cacheN] = !isBitmapLoaded; | ||
// put on the top of the stack | ||
nextStackLevel++; | ||
cacheStackLevel[cacheN] = nextStackLevel; | ||
// refresh | ||
hasLoader = false; | ||
callback.onBitmapCacheLoad(); | ||
} | ||
} | ||
} |
Oops, something went wrong.