├── .gitignore
├── .idea
├── assetWizardSettings.xml
├── caches
│ ├── deviceStreaming.xml
│ └── gradle_models.ser
├── codeStyles
│ └── Project.xml
├── compiler.xml
├── deploymentTargetSelector.xml
├── dictionaries
│ └── bojko108.xml
├── gradle.xml
├── migrations.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── .project
├── .settings
└── org.eclipse.buildship.core.prefs
├── HOMEPAGE.md
├── LICENSE
├── README.md
├── app
├── .classpath
├── .gitignore
├── .project
├── .settings
│ └── org.eclipse.buildship.core.prefs
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ ├── java
│ │ └── com
│ │ │ └── bojko108
│ │ │ └── mobiletileserver
│ │ │ └── ExampleInstrumentedTest.java
│ └── test
│ │ └── java
│ │ └── com
│ │ └── bojko108
│ │ └── mobiletileserver
│ │ └── ExampleUnitTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── java
│ └── com
│ │ └── bojko108
│ │ └── mobiletileserver
│ │ ├── InfoActivity.java
│ │ ├── MainActivity.java
│ │ ├── SettingsActivity.java
│ │ ├── ShortcutsActivity.java
│ │ ├── server
│ │ ├── ServerFiles.java
│ │ ├── TileServer.java
│ │ ├── TileService.java
│ │ ├── TileServiceReceiver.java
│ │ └── tilesets
│ │ │ ├── MBTilesDatabase.java
│ │ │ ├── StaticFileInfo.java
│ │ │ └── TilesetInfo.java
│ │ └── utils
│ │ ├── BatteryOptimization.java
│ │ ├── HelperClass.java
│ │ └── TileGrid.java
│ └── res
│ ├── drawable-anydpi
│ └── ic_not_stop.xml
│ ├── drawable-hdpi
│ └── ic_stat_name.png
│ ├── drawable-mdpi
│ └── ic_stat_name.png
│ ├── drawable-xhdpi
│ └── ic_stat_name.png
│ ├── drawable-xxhdpi
│ └── ic_stat_name.png
│ ├── drawable
│ ├── ic_help_24dp.xml
│ ├── ic_info_black_24dp.xml
│ ├── ic_notifications_black_24dp.xml
│ ├── ic_settings_24dp.xml
│ ├── ic_start_24dp.xml
│ ├── ic_stop_24dp.xml
│ ├── ic_sync_black_24dp.xml
│ ├── icon_info.png
│ ├── img_tiles.png
│ ├── shortcut_browse_icon.png
│ ├── shortcut_navigate_icon.png
│ ├── shortcut_start_icon.png
│ └── shortcut_stop_icon.png
│ ├── layout
│ ├── activity_info.xml
│ ├── activity_main.xml
│ └── activity_settings.xml
│ ├── menu
│ └── main_menu.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── raw
│ ├── badrequest.html
│ ├── home.html
│ ├── internalservererror.html
│ ├── mbtilesserviceinfo.txt
│ ├── no_tile.png
│ ├── oruxmaps.txt
│ ├── preview.html
│ ├── services.html
│ ├── staticfileinfo.txt
│ ├── staticfiles.html
│ └── tileserviceinfo.txt
│ ├── values-bg-rBG
│ └── strings.xml
│ ├── values-bg
│ └── strings.xml
│ ├── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ic_launcher_background.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ ├── preferences.xml
│ └── shortcuts.xml
├── build.gradle
├── docs
├── 01.jpg
├── 02.jpg
├── 03.jpg
├── 04.jpg
├── 05.jpg
├── 06.jpg
├── 07.jpg
├── README.md
├── feature-graphic.jpg
├── icon.cdr
├── icon.png
└── no_tile.png
├── fastlane
└── metadata
│ └── android
│ ├── de
│ └── short_description.txt
│ └── en-US
│ ├── full_description.txt
│ ├── images
│ ├── featureGraphic.jpg
│ ├── icon.png
│ └── phoneScreenshots
│ │ ├── 01.jpg
│ │ ├── 02.jpg
│ │ ├── 03.jpg
│ │ ├── 04.jpg
│ │ ├── 05.jpg
│ │ ├── 06.jpg
│ │ └── 07.jpg
│ └── short_description.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | app/release
9 | app/debug
10 | /captures
11 | .externalNativeBuild
12 | SIGN_APK.md
--------------------------------------------------------------------------------
/.idea/assetWizardSettings.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
25 |
30 |
45 | |
|
6 |
7 | Mobile Tile Server is a local HTTP server, serving Map Tiles from the device storage. When the server is running you can access the map tiles from different mapping applications. The application provides access to two types of tilesets:
8 |
9 | - Map Tiles stored in directories - [Slippy Maps](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames)
10 | - Map Tiles stored in MBTiles files - [MBTiles files](https://github.com/mapbox/mbtiles-spec)
11 |
12 | Simmilar map tiles server is available for Windows (with NodeJS) - [Windows Tile Server](https://github.com/bojko108/windows-tile-server).
13 |
14 | ## Contents
15 |
16 | - [Info](#info)
17 | - [Available HTTP routes](#available-http-routes)
18 | - [Directory Tilesets](#directory-tilesets)
19 | - [Get a list of available Directory Tilesets](#get-a-list-of-available-directory-tilesets)
20 | - [MBTiles Tilesets](#mbtiles-tilesets)
21 | - [Get a list of available MBTiles Tilesets](#get-a-list-of-available-mbtiles-tilesets)
22 | - [Preview Tilesets](#preview-tilesets)
23 | - [Examples](#examples)
24 | - [Mobile Geodesy App](#mobile-geodesy-app)
25 | - [OruxMaps App](#oruxmaps-app)
26 | - [Dependencies](#dependencies)
27 |
28 | ## Info
29 |
30 | The map tiles must be stored in device storage and the app should have access to the raw files. In app settings you can change the root directory, where the tilesets are stored and also the server's listening port. When the server is running all tilesets from the root directory can be accessed using HTTP GET Requests.
31 |
32 | ---
33 |
34 | > ⚠️ The tile server is running in background and battery optimization functions in Android can cause it to become inactive after time. In order to be able to use it for a longer periods of time it is recommended to enable manual settings for **Mobile Tile Server** App in Device Settings > Battery > App Launch.
35 |
36 | ### Available HTTP routes
37 |
38 | - `http://localhost:{port}/` - this is the home address of the server where you can get usefull infromation on how to use the tilesets in your mapping applications
39 | - `http://localhost:{port}/preview/mbtiles?tileset={tileset}` - preview a MBTiles tileset, where `tileset` query parameter sets the name of the MBTiles file
40 | - `http://localhost:{port}/preview/tiles?tileset={tileset}` - preview a Directory tileset, where `tileset` query parameter sets the name of the directory
41 | - `http://localhost:{port}/mbtiles` - list all available MBTiles tilesets, served by the server
42 | - `http://localhost:{port}/mbtiles?tileset={tileset}&z={z}&x={x}&y={y}` - returns a map tile from a MBTiles tileset, where `tileset` query parameter sets the name of the MBTiles file; `z`, `x` and `y` represents [tile coordinates](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames) - if your mapping application uses [TMS Schema](https://github.com/mapbox/mbtiles-spec/blob/master/1.3/spec.md#content-1) use nagative values for `y`.
43 | - `http://localhost:{port}/tiles` - list all available Directory tilesets, served by the server
44 | - `http://localhost:{port}/tiles/{tileset}/{z}/{x}/{y}.png` - returns a map tile from a Directory tileset, where `tileset` query parameters sets the name of the directory; `z`, `x` and `y` represents [tile coordinates](https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames).
45 | - `http://localhost:{port}/availabletilesets` - returns all available tilesets in JSON format - check the example use with [Mobile Geodesy App](#mobile-geodesy-app) for more details.
46 | - `http://localhost:{port}/static` - returns all available static files for download
47 | - `http://localhost:{port}/static?filename={file_name}` - gets the specified static file
48 |
49 | ## Default no-tile image
50 |
51 | Default no-tile image (image served when a map tile is not available) can be changed by puting a `no_tile.png` file in your root directory:
52 |
53 | ```
54 | 📦MobileTileServer --> server root directory
55 | ┣ 📂tiles
56 | ┣ 📂mbtiles
57 | ┗ 📜no_tile.png --> default no-tile image to be used
58 | ```
59 |
60 | ## Directory Tilesets
61 |
62 | Directory Tilesets are image files stored in directories. Each zoom level (`z` coordinate) is stored in a separate subdirectory and each tile column (`x` coordinate) is stored in additional subdirectory. This is an example file structure of the root directory:
63 |
64 | ```
65 | 📦MobileTileServer --> server root directory
66 | ┣ 📂tiles
67 | ┃ ┣ 📂default --> tileset name
68 | ┃ ┃ ┣ 📂1 --> zoom level (z coordinate)
69 | ┃ ┃ ┃ ┗ 📂1 --> x coordinate
70 | ┃ ┃ ┃ ┃ ┗ 📜0.png --> map tile (y coordinate)
71 | ┃ ┃ ┣ 📂2
72 | ┃ ┃ ┃ ┗ 📂2
73 | ┃ ┃ ┃ ┃ ┗ 📜1.png
74 | ┃ ┃ ┃ ┗ ...
75 | ┃ ┃ ┗ ...
76 | ┃ ┗ ...
77 | ┗ 📂mbtiles
78 | ```
79 |
80 | #### Access
81 |
82 | Add the following url to your mapping application:
83 |
84 | ```
85 | http://localhost:{port}/tiles/{tileset}/{z}/{x}/{y}.png
86 | ```
87 |
88 | - `tileset` - represents the name of the directory
89 | - `z` - represents the zoom level
90 | - `x` - represents tile column
91 | - `y` - represents tile row - if your mapping application uses TMS schema - set this value as negative
92 |
93 | You can go to the [examples](#examples) for more details on how to use the tilesets.
94 |
95 | ### Get a list of available Directory Tilesets
96 |
97 | You can list all available Directory Tilesets by going to:
98 |
99 | ```
100 | http://localhost:{port}/tiles
101 | ```
102 |
103 | This will return a list of all available Directory Tilesets, served from this server as well as additional information, describing the parameters of the tilesets:
104 |
105 | ### Example Repsonse
106 |
107 | > **Available Directory Tilesets**
108 | >
109 | > This is a list of all directories, containing tiles and are served from this service:
110 | >
111 | > - **default** - preview with Leaflet viewer
112 | >
113 | > | Property | Value |
114 | > | ------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
115 | > | Min Zoom | 1 |
116 | > | Max Zoom | 12 |
117 | > | Bounds | 22.0,40.5,28.0,45.0 (unable to calculate, default value is returned) |
118 | > | Center | 25.0,42.75,6.0 (unable to calculate, default value is returned) |
119 | > | For use in Mobile Geodesy | Tilesets are automatically added to Mobile Geodesy App. Just start the tile server, open Mobile Geodesy and choose desired tileset to load it as a basemap. |
120 | > | For use in OruxMaps | XML text to be added to OruxMaps App configuration file stored in: _/storage/emulated/0/oruxmaps/mapfiles/onlinemapsources.xml_ |
121 |
122 | ## MBTiles Tilesets
123 |
124 | MBTiles Tilesets are SQLite databases with known schema - [MBTiles](https://github.com/mapbox/mbtiles-spec). This is an example file structure of the root directory:
125 |
126 | ```
127 |
128 | 📦MobileTileServer --> server root directory
129 | ┣ 📂mbtiles
130 | ┃ ┣ 📜glavatar-kaleto-M5000-zoom1_17.mbtiles --> MBTiles Tileset
131 | ┃ ┣ 📜glavatar-kaleto-orthophoto-zoom1_18.mbtiles --> MBTiles Tileset
132 | ┃ ┗ ...
133 | ┗ 📂tiles
134 |
135 | ```
136 |
137 | #### Access
138 |
139 | Add the following url to your mapping application:
140 |
141 | ```
142 |
143 | `http://localhost:{port}/mbtiles?tileset={tileset}&z={z}&x={x}&y={y}
144 |
145 | ```
146 |
147 | - `tileset` - represents the name of the directory
148 | - `z` - represents the zoom level
149 | - `x` - represents tile column
150 | - `y` - represents tile row - if your mapping application uses TMS schema - set this value as negative
151 |
152 | You can go to the [examples](#examples) for more details on how to use the tilesets.
153 |
154 | ### Get a list of available MBTiles Tilesets
155 |
156 | You can list all available MBTIles Tilesets by going to:
157 |
158 | ```
159 | http://localhost:{port}/mbtiles
160 | ```
161 |
162 | This will return a list of all available MBTiles Tilesets, served from this server as well as additional information, describing the parameters of the tilesets:
163 |
164 | ### Example Repsonse
165 |
166 | > **Available MBTiles Tilesets**
167 | >
168 | > This is a list of all MBTiles tilesets, served from this server:
169 | >
170 | > - **glavatar-kaleto-M5000-zoom1_17.mbtiles** - preview with Leaflet viewer
171 | >
172 | > | Property | Value |
173 | > | ------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
174 | > | Name | glavatar-kaleto-1-17 |
175 | > | Version | 1.1 |
176 | > | Description | Glavatar - Kaleto, ETK, Scale 1:5000 |
177 | > | Min Zoom | 1 |
178 | > | Max Zoom | 17 |
179 | > | Bounds | 24.762587288428623,42.29438246738081,25.033514158464705,42.50673679522477 |
180 | > | Center | 24.898050723446666,42.400559631302784,10.0 |
181 | > | For use in Mobile Geodesy | Tilesets are automatically added to Mobile Geodesy App. Just start the tile server, open Mobile Geodesy and choose desired tileset to load it as a basemap. |
182 | > | For use in OruxMaps | XML text to be added to OruxMaps App configuration file stored in: _/storage/emulated/0/oruxmaps/mapfiles/onlinemapsources.xml_ |
183 |
184 | ## Static files
185 |
186 | Returns a list of all static files served by this server. This is an example file structure of the root directory:
187 |
188 | ```
189 |
190 | 📦MobileTileServer --> server root directory
191 | ┣ 📂static
192 | ┃ ┣ 📜cez.json --> static file
193 | ┃ ┣ 📜test.dwg --> another static file
194 | ┃ ┗ ...
195 | ┗ 📂tiles
196 |
197 | ```
198 |
199 | ### Get a list of all available static files
200 |
201 | You can list all available static files by going to:
202 |
203 | ```
204 | http://localhost:{port}/static
205 | ```
206 |
207 | This will return a list of all available static files, served from this server as well as additional information, describing the parameters of the files:
208 |
209 | ### Example Repsonse
210 |
211 | > **Available Static Files**
212 | >
213 | > This is a list of all static files, which are served from this service:
214 | >
215 | > - **cez.json** - download file
216 | >
217 | > | Property | Value |
218 | > | ------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
219 | > | Content Type | application/json |
220 | > | Size | 6 KB
221 |
222 | ## Preview Tilesets
223 |
224 | All available tilesets can be previewed in a simple Map Viewer created with [Leaflet](https://leafletjs.com). Each type of tileset is accessible on different address:
225 |
226 | ### Directory Tilesets
227 |
228 | Navigate to:
229 |
230 | ```
231 | http://localhost:{port}/preview/tiles?tileset={tileset}
232 | ```
233 |
234 | where `tileset` is the name of the directory containing the map tiles.
235 |
236 | ### MBTiles Tilesets
237 |
238 | Navigate to:
239 |
240 | ```
241 | http://localhost:{port}/preview/mbtiles?tileset={tileset}
242 | ```
243 |
244 | where `tileset` is the name of the MBTiles file containing the map tiles.
245 |
246 | ## Static files
247 |
248 | Navigate to:
249 |
250 | ```
251 | http://localhost:{port}/static?filename={file_name}
252 | ```
253 |
254 | where `file_name` is the name of the static file to return.
255 |
256 | ## Examples
257 |
258 | ### Mobile Geodesy App
259 |
260 | [Mobile Geodesy](https://github.com/bojko108/mobile-geodesy) is a mapping application, which can be used for collecting data and navigation. The application is capable of displaying tilesets, served from this tile server. At runtime, the Mobile Geodesy App will connect to `http://localhost:{port}/availabletilesets` and will download the list of all available tilesets. Then in the map activity, you can select the desired tileset to load as a basemap.
261 |
262 | ### OruxMaps App
263 |
264 | [OruxMaps](https://www.oruxmaps.com/cs/en/) is another mapping application with lots of tools and capabilities. The app is also capable of loading tilesets by adding the needed infromation in app's configuration file, stored in `/storage/emulated/0/oruxmaps/mapfiles/onlinemapsources.xml`. There you can add additional map sources, which can be then used in the application as basemaps. This is an example:
265 |
266 | 1. Open the configuration file, stored in `/storage/emulated/0/oruxmaps/mapfiles/onlinemapsources.xml`.
267 | 2. Add this XML to the file:
268 |
269 | ```xml
270 |
175 | * If the app does not has permission then the user will be prompted to grant permissions 176 | * 177 | * @param activity this activity 178 | */ 179 | private void checkPermissions(Activity activity) { 180 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { 181 | if (Environment.isExternalStorageManager()) { 182 | //startActivity(new Intent(this, MainActivity.class)); 183 | } else { 184 | //request for the permission 185 | AlertDialog alertDialog = new AlertDialog.Builder(this) 186 | .setTitle("Action required") 187 | .setMessage("This application needs access to all files on this device. Access to all files located in the root directory (set in app settings) is needed. In the next dialog find Mobile Tile Server application and give it permission to manage all files.") 188 | .setPositiveButton("OK", new DialogInterface.OnClickListener() { 189 | @Override 190 | public void onClick(DialogInterface dialogInterface, int i) { 191 | Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION); 192 | Uri uri = Uri.fromParts("package", getPackageName(), null); 193 | intent.setData(uri); 194 | startActivity(intent); 195 | } 196 | }) 197 | .setNegativeButton("No", new DialogInterface.OnClickListener() { 198 | @Override 199 | public void onClick(DialogInterface dialogInterface, int i) { 200 | Toast.makeText(getApplicationContext(),"Permission not given! The tile server will serve only static map tiles.",Toast.LENGTH_LONG).show(); 201 | } 202 | }) 203 | .show(); 204 | } 205 | } else { 206 | //below android 11 207 | int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE); 208 | 209 | if (PackageManager.PERMISSION_GRANTED != permission) { 210 | // ask user for permissions 211 | ActivityCompat.requestPermissions( 212 | activity, 213 | PERMISSIONS_STORAGE, 214 | REQUEST_EXTERNAL_STORAGE 215 | ); 216 | } 217 | } 218 | 219 | } 220 | 221 | /** 222 | * This local broadcast is used to simply ping {@link TileService} to see if 223 | * it is running 224 | */ 225 | protected BroadcastReceiver localReceiver = new BroadcastReceiver() { 226 | @Override 227 | public void onReceive(Context context, Intent intent) { 228 | if (TileService.ACTION_RUNNING.equals(intent.getAction())) { 229 | running = true; 230 | } 231 | } 232 | }; 233 | } 234 | -------------------------------------------------------------------------------- /app/src/main/java/com/bojko108/mobiletileserver/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.bojko108.mobiletileserver; 2 | 3 | import android.content.DialogInterface; 4 | import android.os.Bundle; 5 | import android.text.InputType; 6 | import android.widget.EditText; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.preference.EditTextPreference; 10 | import androidx.preference.Preference; 11 | import androidx.preference.PreferenceManager; 12 | import androidx.appcompat.app.AppCompatActivity; 13 | import androidx.preference.PreferenceFragmentCompat; 14 | 15 | import com.bojko108.mobiletileserver.utils.BatteryOptimization; 16 | import com.google.android.material.dialog.MaterialAlertDialogBuilder; 17 | 18 | 19 | public class SettingsActivity extends AppCompatActivity { 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | setContentView(R.layout.activity_settings); 24 | getSupportFragmentManager() 25 | .beginTransaction() 26 | .replace(R.id.content, new SettingsFragment()) 27 | .commit(); 28 | } 29 | 30 | public static class SettingsFragment extends PreferenceFragmentCompat implements Preference.OnPreferenceClickListener, EditTextPreference.OnBindEditTextListener { 31 | @Override 32 | public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 33 | setPreferencesFromResource(R.xml.preferences, rootKey); 34 | findPreference(getString(R.string.settings_open_settings_button)).setOnPreferenceClickListener(this); 35 | findPreference(getString(R.string.settings_reset_button)).setOnPreferenceClickListener(this); 36 | findPreference(getString(R.string.rootpath)).setOnPreferenceClickListener(this); 37 | 38 | ((EditTextPreference) getPreferenceManager().findPreference(getResources().getString(R.string.serverport))).setOnBindEditTextListener(this); 39 | } 40 | 41 | @Override 42 | public boolean onPreferenceClick(Preference preference) { 43 | if (getString(R.string.settings_reset_button).equals(preference.getKey())) { 44 | MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getContext()); 45 | builder.setTitle("Reset preferences") 46 | .setMessage(getString(R.string.restore_defaults)) 47 | .setNegativeButton(getString(R.string.dialog_no), null) 48 | .setPositiveButton(getString(R.string.dialog_yes), new DialogInterface.OnClickListener() { 49 | @Override 50 | public void onClick(DialogInterface dialogInterface, int i) { 51 | // restore default settings when "Reset" is clicked 52 | PreferenceManager.getDefaultSharedPreferences(getContext()).edit().clear().commit(); 53 | PreferenceManager.setDefaultValues(getContext(), R.xml.preferences, true); 54 | // close this activity so the settings get updated 55 | getActivity().finish(); 56 | } 57 | }) 58 | .create(); 59 | 60 | builder.show(); 61 | } else if (getString(R.string.settings_open_settings_button).equals(preference.getKey())) { 62 | BatteryOptimization.openSettings(getContext()); 63 | } 64 | 65 | return true; 66 | } 67 | 68 | @Override 69 | public void onBindEditText(@NonNull EditText editText) { 70 | editText.setInputType(InputType.TYPE_CLASS_NUMBER); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/bojko108/mobiletileserver/ShortcutsActivity.java: -------------------------------------------------------------------------------- 1 | package com.bojko108.mobiletileserver; 2 | 3 | import android.app.Activity; 4 | import android.content.Intent; 5 | import android.content.SharedPreferences; 6 | import android.os.Bundle; 7 | import android.preference.PreferenceManager; 8 | 9 | import com.bojko108.mobiletileserver.server.TileService; 10 | import com.bojko108.mobiletileserver.server.TileServiceReceiver; 11 | 12 | public class ShortcutsActivity extends Activity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | 18 | Intent intent = new Intent(this, TileServiceReceiver.class); 19 | 20 | if (TileServiceReceiver.ACTION_START.equals(getIntent().getAction())) { 21 | SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); 22 | int serverPort = Integer.parseInt(prefs.getString("serverport", getResources().getString(R.string.settings_server_port_default))); 23 | String rootPath = prefs.getString("rootpath", getResources().getString(R.string.settings_root_path_default)); 24 | 25 | intent.setAction(TileServiceReceiver.ACTION_START); 26 | intent.putExtra(TileService.KEY_SERVER_PORT, serverPort); 27 | intent.putExtra(TileService.KEY_ROOT_PATH, rootPath); 28 | } else if (TileServiceReceiver.ACTION_STOP.equals(getIntent().getAction())) { 29 | intent.setAction(TileServiceReceiver.ACTION_STOP); 30 | } 31 | 32 | if (intent.getAction() != null) { 33 | sendBroadcast(intent); 34 | } 35 | 36 | finish(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/bojko108/mobiletileserver/server/TileService.java: -------------------------------------------------------------------------------- 1 | package com.bojko108.mobiletileserver.server; 2 | 3 | import android.app.Notification; 4 | import android.app.NotificationChannel; 5 | import android.app.NotificationManager; 6 | import android.app.PendingIntent; 7 | import android.app.Service; 8 | import android.content.BroadcastReceiver; 9 | import android.content.Context; 10 | import android.content.Intent; 11 | import android.content.IntentFilter; 12 | import android.graphics.Color; 13 | import android.graphics.drawable.Icon; 14 | import android.os.Build; 15 | import android.os.IBinder; 16 | import android.util.Log; 17 | 18 | import com.bojko108.mobiletileserver.MainActivity; 19 | import com.bojko108.mobiletileserver.R; 20 | 21 | import androidx.annotation.Nullable; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 27 | 28 | /** 29 | * This service controls the Mobile Tile Server. Use {@link TileServiceReceiver TileServiceReceiver class} 30 | * for more info about interacting with this service. For more info regarding the HTTP Server go to 31 | * {@link TileServer TileServer class}. 32 | *
33 | * Startup parameters: 34 | *40 | * Mobile Tile Server, Copyright (c) 2020 by bojko108 41 | *
42 | */ 43 | public class TileService extends Service { 44 | private static final String TAG = TileService.class.getName(); 45 | /** 46 | * Use this key to trigger ping action to see if {@link TileService} is running. 47 | * A receiver is registered in {@link TileService} to listen for this action and 48 | * to broadcast {@link TileService#ACTION_RUNNING} task if the service is running. 49 | */ 50 | public static final String ACTION_PING = "mobiletileserver.ACTION_PING"; 51 | /** 52 | * Use this key to register a local receiver which will help you to determine if {@link TileService} 53 | * is running. First register a local receiver with {@link IntentFilter} set to this action 54 | * and then broadcast {@link TileService#ACTION_PING} to trigger the process. 55 | */ 56 | public static final String ACTION_RUNNING = "mobiletileserver.ACTION_RUNNING"; 57 | 58 | /** 59 | * Use this key to set the server URL address as String 60 | */ 61 | public static final String KEY_SERVER_PATH = "KEY_SERVER_PATH"; 62 | /** 63 | * Use this key to set the server listening port as int 64 | */ 65 | public static final String KEY_SERVER_PORT = "KEY_SERVER_PORT"; 66 | /** 67 | * Use this key to set the server root directory path as String 68 | */ 69 | public static final String KEY_ROOT_PATH = "KEY_ROOT_PATH"; 70 | 71 | /** 72 | * NotificationChannel used by this Service 73 | */ 74 | private NotificationChannel notificationChannel; 75 | /** 76 | * Reference to the HTTP Server 77 | */ 78 | private TileServer server; 79 | 80 | @Override 81 | public void onCreate() { 82 | super.onCreate(); 83 | 84 | Log.i(TAG, "onCreate"); 85 | } 86 | 87 | @Nullable 88 | @Override 89 | public IBinder onBind(Intent intent) { 90 | return null; 91 | } 92 | 93 | @Override 94 | public int onStartCommand(Intent intent, int flags, int startId) { 95 | super.onStartCommand(intent, flags, startId); 96 | 97 | Log.i(TAG, "onStartCommand"); 98 | try { 99 | LocalBroadcastManager.getInstance(this).registerReceiver(this.localReceiver, new IntentFilter(ACTION_PING)); 100 | 101 | if (this.server == null) { 102 | String rootPath = intent.getStringExtra(KEY_ROOT_PATH); 103 | int port = intent.getIntExtra(KEY_SERVER_PORT, 9999); 104 | 105 | this.server = new TileServer(rootPath, getApplicationContext()); 106 | this.server.start(port); 107 | 108 | startForeground(1, this.createNotification()); 109 | } 110 | } catch (Exception e) { 111 | Log.e(TAG, "onStartCommand throws: ", e); 112 | } 113 | 114 | return START_REDELIVER_INTENT; 115 | } 116 | 117 | @Override 118 | public void onDestroy() { 119 | super.onDestroy(); 120 | 121 | Log.i(TAG, "onDestroy"); 122 | 123 | LocalBroadcastManager.getInstance(this).unregisterReceiver(this.localReceiver); 124 | 125 | if (this.server != null) { 126 | this.server.stop(); 127 | } 128 | 129 | stopForeground(true); 130 | } 131 | 132 | /** 133 | * Creates a notification channel for this service 134 | */ 135 | private void createNotificationChannel() { 136 | this.notificationChannel = new NotificationChannel(TAG, getResources().getString(R.string.app_name), NotificationManager.IMPORTANCE_HIGH); 137 | this.notificationChannel.setLightColor(Color.WHITE); 138 | this.notificationChannel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC); 139 | this.notificationChannel.setSound(null, null); 140 | NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); 141 | assert manager != null; 142 | manager.createNotificationChannel(this.notificationChannel); 143 | } 144 | 145 | /** 146 | * Creates a new notification to the notification channel 147 | * 148 | * @return notification 149 | */ 150 | private Notification createNotification() { 151 | if (this.notificationChannel == null) { 152 | this.createNotificationChannel(); 153 | } 154 | 155 | Intent openIntent = new Intent(this, MainActivity.class); 156 | openIntent.setAction(Intent.ACTION_MAIN); 157 | openIntent.addCategory(Intent.CATEGORY_LAUNCHER); 158 | 159 | PendingIntent pendingIntentNotification = PendingIntent 160 | .getActivity(this, 0, openIntent, PendingIntent.FLAG_IMMUTABLE); 161 | 162 | Notification.Action stopAction = this.createAction( 163 | TileServiceReceiver.ACTION_STOP, getResources().getString(R.string.server_action_stop_short), null, PendingIntent.FLAG_IMMUTABLE); 164 | 165 | List21 | * Mobile Tile Server, Copyright (c) 2020 by bojko108 22 | *
23 | */ 24 | public class TileServiceReceiver extends BroadcastReceiver { 25 | /** 26 | * Use this key to trigger Start Server action as String 27 | */ 28 | public static final String ACTION_START = "mobiletileserver.ACTION_START"; 29 | /** 30 | * Use this key to trigger Stop Server action as String 31 | */ 32 | public static final String ACTION_STOP = "mobiletileserver.ACTION_STOP"; 33 | /** 34 | * Use this key to trigger Open Root Directory action in FileExplorer as String 35 | */ 36 | public static final String ACTION_OPEN_ROOT_PATH = "mobiletileserver.ACTION_OPEN_ROOT_PATH"; 37 | /** 38 | * Use this key to trigger Open Server Home Page action in WebBrowser as String 39 | */ 40 | public static final String ACTION_NAVIGATE_TO_SERVER = "mobiletileserver.ACTION_NAVIGATE_TO_SERVER"; 41 | 42 | @Override 43 | public void onReceive(Context context, Intent intent) { 44 | String action = intent.getAction(); 45 | if (action != null) { 46 | try { 47 | String serverUrl = intent.getStringExtra(TileService.KEY_SERVER_PATH); 48 | String rootPath = intent.getStringExtra(TileService.KEY_ROOT_PATH); 49 | int serverPort = intent.getIntExtra(TileService.KEY_SERVER_PORT, 1886); 50 | switch (action) { 51 | case ACTION_START: 52 | Intent serviceToStart = new Intent(context, TileService.class); 53 | serviceToStart.putExtra(TileService.KEY_SERVER_PORT, serverPort); 54 | serviceToStart.putExtra(TileService.KEY_ROOT_PATH, rootPath); 55 | context.startService(serviceToStart); 56 | break; 57 | case ACTION_STOP: 58 | Intent serviceToStop = new Intent(context, TileService.class); 59 | context.stopService(serviceToStop); 60 | break; 61 | case ACTION_OPEN_ROOT_PATH: 62 | if (rootPath != null) { 63 | Uri selectedUri = Uri.parse(rootPath); 64 | Intent explorerIntent = new Intent(Intent.ACTION_VIEW); 65 | explorerIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 66 | explorerIntent.setDataAndType(selectedUri, "resource/folder"); 67 | if (explorerIntent.resolveActivityInfo(context.getPackageManager(), 0) != null) { 68 | context.startActivity(explorerIntent); 69 | this.closeNotificationBar(context); 70 | } 71 | } 72 | break; 73 | case ACTION_NAVIGATE_TO_SERVER: 74 | if (serverUrl != null) { 75 | Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(serverUrl)); 76 | browserIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 77 | context.startActivity(browserIntent); 78 | this.closeNotificationBar(context); 79 | } 80 | break; 81 | default: 82 | break; 83 | } 84 | } catch (Exception ex) { 85 | Toast.makeText(context, ex.getMessage(), Toast.LENGTH_LONG).show(); 86 | } 87 | } 88 | } 89 | 90 | /** 91 | * Broadcasts Intent.ACTION_CLOSE_SYSTEM_DIALOGS message, which will close the notification bar. 92 | * 93 | * @param context context to be used for broadcasting Intent.ACTION_CLOSE_SYSTEM_DIALOGS message 94 | */ 95 | private void closeNotificationBar(Context context) { 96 | //context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); 97 | } 98 | } -------------------------------------------------------------------------------- /app/src/main/java/com/bojko108/mobiletileserver/server/tilesets/MBTilesDatabase.java: -------------------------------------------------------------------------------- 1 | package com.bojko108.mobiletileserver.server.tilesets; 2 | 3 | import java.util.Locale; 4 | 5 | import android.content.Context; 6 | import android.database.Cursor; 7 | import android.database.sqlite.SQLiteDatabase; 8 | import android.database.sqlite.SQLiteOpenHelper; 9 | 10 | import androidx.annotation.Nullable; 11 | 12 | public class MBTilesDatabase extends SQLiteOpenHelper { 13 | private static final String GET_TILE_SQL_STRING = "SELECT \"tile_data\" FROM \"tiles\" where zoom_level = %d and tile_column=%d and tile_row=%d"; 14 | private static final String GET_INFO_SQL_STRING = "SELECT * FROM \"metadata\""; 15 | 16 | private TilesetInfo info; 17 | 18 | public MBTilesDatabase(Context context, String databaseName) { 19 | super(context, databaseName, null, 1); 20 | this.info = this.readInfo(databaseName); 21 | } 22 | 23 | @Override 24 | public void onCreate(SQLiteDatabase db) { 25 | } 26 | 27 | @Override 28 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 29 | } 30 | 31 | public TilesetInfo getInfo() { 32 | return this.info; 33 | } 34 | 35 | public byte[] getTile(int z, int x, int y) { 36 | byte[] result = null; 37 | SQLiteDatabase db = this.getReadableDatabase(); 38 | 39 | // MBTiles by default use TMS for the tiles. Most mapping apps use slippy maps: XYZ schema. 40 | // We need to handle both. 41 | if (y > 0) { 42 | y = (int) Math.pow(2, z) - Math.abs(y) - 1; 43 | } else { 44 | y = Math.abs(y); 45 | } 46 | 47 | String sql = String.format(Locale.getDefault(), GET_TILE_SQL_STRING, z, x, y); 48 | 49 | try (Cursor cur = db.rawQuery(sql, null)) { 50 | cur.moveToFirst(); 51 | if (!cur.isAfterLast()) { 52 | result = cur.getBlob(0); 53 | } 54 | } catch (Exception ex) { 55 | ex.printStackTrace(); 56 | } 57 | 58 | return result; 59 | } 60 | 61 | private TilesetInfo readInfo(String fileName) { 62 | SQLiteDatabase db = this.getReadableDatabase(); 63 | String[] path = fileName.split("/"); 64 | 65 | TilesetInfo info = new TilesetInfo(); 66 | info.setParameter(TilesetInfo.TILESET_NAME, path[path.length - 1]); 67 | 68 | try (Cursor cur = db.rawQuery(GET_INFO_SQL_STRING, null)) { 69 | cur.moveToFirst(); 70 | while (!cur.isAfterLast()) { 71 | String name = cur.getString(cur.getColumnIndex("name")); 72 | String value = cur.getString(cur.getColumnIndex("value")); 73 | info.setParameter(name, value); 74 | cur.moveToNext(); 75 | } 76 | } catch (Exception ex) { 77 | ex.printStackTrace(); 78 | } 79 | 80 | return info; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/com/bojko108/mobiletileserver/server/tilesets/StaticFileInfo.java: -------------------------------------------------------------------------------- 1 | package com.bojko108.mobiletileserver.server.tilesets; 2 | 3 | import android.text.format.Formatter; 4 | import android.webkit.MimeTypeMap; 5 | 6 | import com.bojko108.mobiletileserver.utils.HelperClass; 7 | 8 | import java.io.File; 9 | import java.util.Locale; 10 | 11 | /** 12 | * This class provides information about a static file, served by this server. 13 | *14 | * Mobile Tile Server, Copyright (c) 2020 by bojko108 15 | *
16 | */ 17 | public class StaticFileInfo { 18 | private File file; 19 | 20 | public StaticFileInfo(String path) { 21 | this.file = HelperClass.getFileFromPath(path); 22 | } 23 | 24 | public StaticFileInfo(File file) { 25 | this.file = file; 26 | } 27 | 28 | public String getFileName() { 29 | return this.file.getName(); 30 | } 31 | 32 | public String getFileNameWithoutExtension() { 33 | String extension = MimeTypeMap.getFileExtensionFromUrl(file.getAbsolutePath()); 34 | return this.getFileName().replace(extension, ""); 35 | } 36 | 37 | public String getContentType() { 38 | return HelperClass.getContentTypeForFile(this.file); 39 | } 40 | 41 | public long getFileSize() { 42 | return this.file.length(); 43 | } 44 | 45 | public String getSizeAsText() { 46 | String units = "B"; 47 | long bytes = this.getFileSize(); 48 | 49 | if (bytes >= 1024) { 50 | bytes = bytes / 1024; // to KB 51 | units = "KB"; 52 | } 53 | 54 | return String.format(Locale.getDefault(), "%d %s", bytes, units); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/bojko108/mobiletileserver/server/tilesets/TilesetInfo.java: -------------------------------------------------------------------------------- 1 | package com.bojko108.mobiletileserver.server.tilesets; 2 | 3 | import com.bojko108.mobiletileserver.utils.HelperClass; 4 | 5 | import org.json.JSONException; 6 | import org.json.JSONObject; 7 | 8 | import java.io.File; 9 | 10 | /** 11 | * This class provides access to various parameters for the tileset stored in 12 | * a MBTiles Database {@link MBTilesDatabase} or a directory with map tiles files. 13 | * Provided parameters from the tileset include: 14 | *26 | * Mobile Tile Server, Copyright (c) 2020 by bojko108 27 | *
28 | */ 29 | public class TilesetInfo { 30 | /** 31 | * Use this key to get/set tileset name 32 | */ 33 | public static final String TILESET_NAME = "tilesetname"; 34 | /** 35 | * Use this key to get/set tileset name parameter from MBTiles Database 36 | */ 37 | public static final String NAME = "name"; 38 | /** 39 | * Use this key to get/set tileset description parameter from MBTiles Database 40 | */ 41 | public static final String DESCRIPTION = "description"; 42 | /** 43 | * Use this key to get/set tileset version parameter from MBTiles Database: 1.1, 1.2, 1.3... 44 | */ 45 | public static final String VERSION = "version"; 46 | /** 47 | * Use this key to get/set tileset type parameter from MBTiles Database: png, pbf... 48 | */ 49 | public static final String FORMAT = "format"; 50 | /** 51 | * Use this key to get/set tileset min zoom parameter 52 | * Specifies the lowest zoom level for which the tileset provides data. 53 | */ 54 | public static final String MIN_ZOOM = "minzoom"; 55 | /** 56 | * Use this key to get/set tileset max zoom parameter. 57 | * Specifies the highest zoom level for which the tileset provides data. 58 | */ 59 | public static final String MAX_ZOOM = "maxzoom"; 60 | /** 61 | * Use this key to set tileset extent in geographic coordinates: 62 | * longitude_min, latitude_min, longitude_max, latitude_max. 63 | * When this parameter is set, the value of {@link TilesetInfo#getCenter()} 64 | * is also calculated. To get the value of bounds parameter use {@link TilesetInfo#getBounds()} 65 | */ 66 | public static final String BOUNDS = "bounds"; 67 | 68 | private String tilesetName = ""; 69 | private String name = ""; 70 | private String description = ""; 71 | private String format = "png"; 72 | private String version = ""; 73 | private int minZoom = 999; 74 | private int maxZoom = -1; 75 | private double[] bounds; 76 | private double[] center; 77 | 78 | public TilesetInfo() { 79 | this(null); 80 | } 81 | 82 | public TilesetInfo(File directory) { 83 | /** 84 | * Default values are set to the extent of Bulgaria! 85 | */ 86 | this.bounds = new double[4]; 87 | this.bounds[0] = 22; 88 | this.bounds[1] = 40.5; 89 | this.bounds[2] = 28; 90 | this.bounds[3] = 45; 91 | this.center = this.calculateCenter(); 92 | 93 | if (directory != null) { 94 | this.tilesetName = directory.getName(); 95 | 96 | // calculate min and max zoom for this tileset 97 | // from the subdirectories with zoom levels 98 | File[] dirs = HelperClass.getDirectoriesFrom(directory); 99 | for (File dir : dirs) { 100 | int zoomLevel = Integer.parseInt(dir.getName()); 101 | maxZoom = Math.max(maxZoom, zoomLevel); 102 | minZoom = Math.min(minZoom, zoomLevel); 103 | } 104 | } 105 | } 106 | 107 | /** 108 | * Get a parameter from the tileset. 109 | * 110 | * @param name of the parameter to return: {@link TilesetInfo#NAME}, 111 | * {@link TilesetInfo#DESCRIPTION}, {@link TilesetInfo#VERSION}, 112 | * {@link TilesetInfo#MIN_ZOOM} or {@link TilesetInfo#MAX_ZOOM}. 113 | * @return parameter value 114 | */ 115 | public18 | * Use the dialog like that: 19 | * final AlertDialog dialog = BatteryOptimizationUtil.getBatteryOptimizationDialog(context); 20 | * if(dialog != null) dialog.show(); 21 | *
22 | * Alter the dialog texts so that they fit your needs. You can provide additional actions that 23 | * should be performed if the positive or negative button are clicked by using the provided method: 24 | * getBatteryOptimizationDialog(Context, OnBatteryOptimizationAccepted, OnBatteryOptimizationCanceled) 25 | *
26 | * Source: https://gist.github.com/moopat/e9735fa8b5cff69d003353a4feadcdbc 27 | *
28 | *
29 | * @author Markus Deutsch @moopat
30 | */
31 | public class BatteryOptimization {
32 |
33 | public static void openSettings(Context context) {
34 | /*
35 | * If there is no resolvable component return right away. We do not use
36 | * isBatteryOptimizationAvailable() for this check in order to avoid checking for
37 | * resolvable components twice.
38 | */
39 | final ComponentName componentName = getResolveableComponentName(context);
40 | if (componentName == null) return;
41 |
42 | final Intent intent = new Intent();
43 | intent.setComponent(componentName);
44 | context.startActivity(intent);
45 | }
46 |
47 | /**
48 | * Get the battery optimization dialog.
49 | * By default the dialog will send the user to the relevant activity if the positive button is
50 | * clicked, and closes the dialog if the negative button is clicked.
51 | *
52 | * @param context Context
53 | * @return the dialog or null if battery optimization is not available on this device
54 | */
55 | @Nullable
56 | public static AlertDialog getBatteryOptimizationDialog(final Context context) {
57 | return getBatteryOptimizationDialog(context,
58 | "Open Battery Optimization", "This app needs to be removed from battery optimizations",
59 | "Cancel", "Ok",
60 | null, null);
61 | }
62 |
63 | /**
64 | * Get the battery optimization dialog.
65 | * By default the dialog will send the user to the relevant activity if the positive button is
66 | * clicked, and closes the dialog if the negative button is clicked. Callbacks can be provided
67 | * to perform additional actions on either button click.
68 | *
69 | * @param context Context
70 | * @param positiveCallback additional callback for the positive button. can be null.
71 | * @param negativeCallback additional callback for the negative button. can be null.
72 | * @return the dialog or null if battery optimization is not available on this device
73 | */
74 | @Nullable
75 | public static AlertDialog getBatteryOptimizationDialog(
76 | final Context context,
77 | final String title,
78 | final String message,
79 | final String negativeButtonText,
80 | final String positiveButtonText,
81 | @Nullable final OnBatteryOptimizationAccepted positiveCallback,
82 | @Nullable final OnBatteryOptimizationCanceled negativeCallback) {
83 | /*
84 | * If there is no resolvable component return right away. We do not use
85 | * isBatteryOptimizationAvailable() for this check in order to avoid checking for
86 | * resolvable components twice.
87 | */
88 | final ComponentName componentName = getResolveableComponentName(context);
89 | if (componentName == null) return null;
90 |
91 | return new AlertDialog.Builder(context)
92 | .setTitle(title)
93 | .setMessage(message)
94 | .setNegativeButton(negativeButtonText, new DialogInterface.OnClickListener() {
95 | @Override
96 | public void onClick(DialogInterface dialog, int which) {
97 | if (negativeCallback != null)
98 | negativeCallback.onBatteryOptimizationCanceled();
99 | }
100 | })
101 | .setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
102 | @Override
103 | public void onClick(DialogInterface dialog, int which) {
104 | if (positiveCallback != null)
105 | positiveCallback.onBatteryOptimizationAccepted();
106 |
107 | final Intent intent = new Intent();
108 | intent.setComponent(componentName);
109 | context.startActivity(intent);
110 | }
111 | }).create();
112 | }
113 |
114 | /**
115 | * Find out if battery optimization settings are available on this device.
116 | *
117 | * @param context Context
118 | * @return true if battery optimization is available
119 | */
120 | public static boolean isBatteryOptimizationAvailable(final Context context) {
121 | return getResolveableComponentName(context) != null;
122 | }
123 |
124 | @Nullable
125 | private static ComponentName getResolveableComponentName(final Context context) {
126 | for (ComponentName componentName : getComponentNames()) {
127 | final Intent intent = new Intent();
128 | intent.setComponent(componentName);
129 | if (context.getPackageManager().resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null)
130 | return componentName;
131 | }
132 | return null;
133 | }
134 |
135 | /**
136 | * Get a list of all known ComponentNames that provide battery optimization on different
137 | * devices.
138 | * Based on Shivam Oberoi's answer on StackOverflow: https://stackoverflow.com/a/48166241/2143225
139 | *
140 | * @return list of ComponentName
141 | */
142 | private static List Mobile Tile Server is a local HTTP server, serving Map Tiles from the device storage. When the server is running you can access the map tiles from different mapping applications. The application provides access to two types of tilesets: The map tiles must be stored in device storage and the app should have access to the raw files. In app settings you can change the root directory, where the tilesets are stored and also the server’s listening port. When the server is running all tilesets from the root directory can be accessed using HTTP GET Requests. ⚠️ The tile server is running in background and battery optimization functions in Android can cause it to become inactive after time. In order to be able to use it for a longer periods of time it is recommended to enable manual settings for Mobile Tile Server App in Device Settings > Battery > App Launch. Directory Tilesets are image files stored in directories. Each zoom level ( MBTiles Tilesets are SQLite databases with known schema - MBTiles. This is an example file structure of the root directory: Returns a list of all static files served by this server. This is an example file structure of the root directory: Mobile Geodesy is a mapping application, which can be used for collecting data and navigation. The application is capable of displaying tilesets, served from this tile server. At runtime, the Mobile Geodesy App will connect to OruxMaps is another mapping application with lots of tools and capabilities. The app is also capable of loading tilesets by adding the needed infromation in app’s configuration file, stored in {{details}} {{details}} Mobile Tile Server can be used as a HTTP server, serving Map Tiles from the device storage. When the server is running you can access the tiles from different mapping applications. The application provides four main options: Access to local Map Tiles Local Map Tiles can be accessed on address: In this case the root directory points to the parent folder (which contains ’Plovdiv’ subfolder). This way you can have multiple subfolders containing different map tiles and all can be accessed through the same server! … can be found on address: As MBTiles use TMS schema to store map tiles, y coordinate must be transformed in order to locate the correct tile row. If your app uses XYZ tile schema, pass negative value for y (-y) as a parameter. There are several parameters, which must be provided: Example: If you have tiles stored in MBTiles format, you can place your files in the root directory and access them with: Redirect can be accessed on address: There are several parameters, which must be provided: Example: If you want to use for example Bing Maps, which uses QuadKey Tile schema and you only have XYZ tile coordinates you can use the redirect option, which will calculate the quadkey value and then will redirect the request to the server. For accessing Bing Maps Aerial map tiles you can navigate to: (400) Bad Request
16 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/home.html:
--------------------------------------------------------------------------------
1 | {{error_message}}
17 | Mobile Tile Server
4 |
6 |
9 | Available HTTP routes
10 | Get a list of all MBTiles Tilesets
11 | Get a list of all Directory Tilesets
12 | Get a list of all Tilesets in JSON format
13 | Get a list of all static files
14 | Info
15 |
17 |
18 |
20 | Directory Tilesets
21 | z
coordinate) is stored in a separate subdirectory and each tile column (x
coordinate) is stored in additional subdirectory. This is an example file structure of the root directory:
36 | 📦MobileTileServer --> server root directory
23 | ┣ 📂tiles
24 | ┃ ┣ 📂default --> tileset name
25 | ┃ ┃ ┣ 📂1 --> zoom level (z coordinate)
26 | ┃ ┃ ┃ ┗ 📂1 --> x coordinate
27 | ┃ ┃ ┃ ┃ ┗ 📜0.png --> map tile (y coordinate)
28 | ┃ ┃ ┣ 📂2
29 | ┃ ┃ ┃ ┗ 📂2
30 | ┃ ┃ ┃ ┃ ┗ 📜1.png
31 | ┃ ┃ ┃ ┗ ...
32 | ┃ ┃ ┗ ...
33 | ┃ ┗ ...
34 | ┗ 📂mbtiles
35 |
MBTiles Tilesets
37 |
47 |
39 | 📦MobileTileServer --> server root directory
40 | ┣ 📂mbtiles
41 | ┃ ┣ 📜glavatar-kaleto-M5000-zoom1_17.mbtiles --> MBTiles Tileset
42 | ┃ ┣ 📜glavatar-kaleto-orthophoto-zoom1_18.mbtiles --> MBTiles Tileset
43 | ┃ ┗ ...
44 | ┗ 📂tiles
45 |
46 |
Static files
48 |
58 |
50 | 📦MobileTileServer --> server root directory
51 | ┣ 📂static
52 | ┃ ┣ 📜cez.json --> static file
53 | ┃ ┣ 📜test.dwg --> another static file
54 | ┃ ┗ ...
55 | ┗ 📂tiles
56 |
57 |
How to use in mapping applications
59 | Mobile Geodesy App
60 | http://localhost:{port}/availabletilesets
and will download the list of all available tilesets. Then in the map activity, you can select the desired tileset to load as a basemap.OruxMaps App
62 | /storage/emulated/0/oruxmaps/mapfiles/onlinemapsources.xml
. There you can add additional map sources, which can be then used in the application as basemaps. This is an example:
64 |
67 | /storage/emulated/0/oruxmaps/mapfiles/onlinemapsources.xml
.
79 | <onlinemapsource uid="FILL_UNIQUE_ID_VALUE">
68 | <name>glavatar-kaleto-orthophoto-zoom1_18</name>
69 | <url><![CDATA[http://localhost:1886/mbtiles?tileset=glavatar-kaleto-orthophoto-zoom1_18.mbtiles&z={z}&x={x}&y={y}]]></url>
70 | <minzoom>1</minzoom>
71 | <maxzoom>18</maxzoom>
72 | <projection>MERCATORESFERICA</projection>
73 | <cacheable>0</cacheable>
74 | <downloadable>1</downloadable>
75 | <maxtilesday>0</maxtilesday>
76 | <maxthreads>0</maxthreads>
77 | </onlinemapsource>
78 |
80 |
82 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/internalservererror.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | (500) Internal Server Error
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/mbtilesserviceinfo.txt:
--------------------------------------------------------------------------------
1 | {{error_message}}
50 | /storage/emulated/0/oruxmaps/mapfiles/onlinemapsources.xml
51 | Do not forget to set the correct uid value in the xml!
52 |
53 | {{header}}
20 |
22 | {{services}}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/staticfileinfo.txt:
--------------------------------------------------------------------------------
1 | {{header}}
20 |
22 | {{static_files}}
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/tileserviceinfo.txt:
--------------------------------------------------------------------------------
1 |
34 | /storage/emulated/0/oruxmaps/mapfiles/onlinemapsources.xml
35 | Do not forget to set the correct uid value in the xml!
36 |
37 | http://localhost:PORT/tiles
– where PORT is set in application settings. In settings, you must specify a directory, where the files are stored. This directory is used as a root for the server. All files in that directory (including subdirectories) will be accessible from the server.
Example: If you have map tiles stored in /storage/emulated/0/MobileTileServer/tiles/Plovdiv/{z}_{x}_{y}.png
, you can set the root directory to: /storage/emulated/0/MobileTileServer
. Then in order to access this map just start the service and navigate to: http://localhost:PORT/tiles/Plovdiv/{z}_{x}_{y}.png
.
Access to local MBTiles fileshttp://localhost:PORT/mbtiles
– where PORT is set in application settings. In settings, you must specify a directory, where the files are stored. This directory is used as a root for the server. All files in that directory (including subdirectories) will be accessible from the server.http://localhost:PORT/mbtiles/?tileset=test.mbtiles&z={z}&x={x}&y={y}
or if XYZ schema is used: http://localhost:PORT/mbtiles/?tileset=test.mbtiles&z={z}&x={x}&y=-{y}
Redirect to a Tile Server with QuadKey Tile schemahttp://localhost:PORT/redirect/?url=&quadkey=true&z=&x=&y=
– where PORT is set in application settings. In settings, you must specify a directory, where the files are stored. This directory is used as a root for the server. All files in that directory (including subdirectories) will be accessible from the server.http://localhost:PORT/redirect/?url=http://ecn.t0.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=6201&quadkey=true&z={z}&x={x}&y={y}