├── .gitignore ├── LICENSE-apache-2.0.txt ├── README ├── library ├── AndroidManifest.xml ├── build.xml ├── project.properties ├── res │ ├── drawable-hdpi │ │ └── ic_launcher.png │ ├── drawable-ldpi │ │ └── ic_launcher.png │ ├── drawable-mdpi │ │ └── ic_launcher.png │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ ├── layout │ │ └── main.xml │ └── values │ │ └── strings.xml └── src │ └── com │ └── sigseg │ └── android │ ├── io │ └── RandomAccessFileInputStream.java │ ├── map │ ├── ImageSurfaceView.java │ └── ImageViewerActivity.java │ └── view │ ├── InputStreamScene.java │ └── Scene.java ├── v3 ├── .gitignore ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ └── world.jpg │ │ ├── java │ │ └── com │ │ │ └── sigseg │ │ │ └── android │ │ │ ├── map │ │ │ ├── ImageSurfaceView.java │ │ │ ├── ImageViewerActivity.kt │ │ │ ├── TouchController.kt │ │ │ ├── TouchState.java │ │ │ └── TouchThread.kt │ │ │ └── view │ │ │ ├── InputStreamScene.java │ │ │ └── Scene.java │ │ └── res │ │ ├── drawable-hdpi │ │ └── ic_launcher.png │ │ ├── drawable-ldpi │ │ └── ic_launcher.png │ │ ├── drawable-mdpi │ │ └── ic_launcher.png │ │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── main.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle └── worldmap ├── AndroidManifest.xml ├── art └── 4973-1209-east-sea.pxm ├── assets ├── layers │ └── ko │ │ └── 4973-1209-east-sea.jpg └── world.jpg ├── build.xml ├── proguard.cfg ├── project.properties └── release ├── 10 └── release │ ├── app-release.apk │ └── output.json ├── 01 ├── WorldMap.apk ├── icon-512x512.jpg ├── shot01.png └── shot02.png ├── 02 ├── WorldMap.apk ├── worldmap-36x36.png ├── worldmap-48x48.png ├── worldmap-512x512.png ├── worldmap-72x72.png ├── worldmap-96x96.png └── worldmap-icon.xcf ├── 03 └── WorldMap.apk ├── 04 └── WorldMap-release.apk └── 05 └── WorldMap-release.apk /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | gen/ 3 | out/ 4 | *.iml 5 | .idea/ 6 | ant.properties 7 | v3/app/release 8 | private-notes 9 | -------------------------------------------------------------------------------- /LICENSE-apache-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | WorldMap is a simple Android app that displays a map of the world taken from Wikimedia (http://upload.wikimedia.org/wikipedia/commons/3/33/Physical_Political_World_Map.jpg), and allows the user to scroll around on it. Note the map is from wikipedia and licensed as public domain (see http://en.wikipedia.org/wiki/File:Physical_Political_World_Map.jpg). 2 | 3 | The map itself is quite large (6480,3888), so it's way too big to fit in memory all at once (6480 x 3888 x 32 / 8) = 100,776,960 -- over 96 megs. The VM heap size Android supports is eith 16 or 24 megs, so we can't fit the whole thing in memory at once. 4 | 5 | So WorldMap uses the BitmapRegionDecoder API (available as of API 10) to decode just what it needs to display. 6 | 7 | WorldMap is available on the Google Play store here: https://play.google.com/store/apps/details?id=com.sigseg.android.worldmap 8 | 9 | Road Map 10 | -------- 11 | * Add flinging to move quickly across the map 12 | 13 | * Work on getting the frame rate up 14 | * Perhaps use SurfaceView 15 | * Or glSurfaceView 16 | 17 | * backport BitmapRegionDecoder to 2.2 or use a different library with JNI. 18 | * on SO, Dianne Hackborn says this is non trivial. 19 | 20 | * Add zooming to infinite levels 21 | * start with the map fully unzoomed 22 | * Calculate where in the world we are once we get to a certain level, then create an intent to start Google Maps. 23 | -------------------------------------------------------------------------------- /library/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /library/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 52 | 56 | 57 | 69 | 70 | 71 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-17 12 | android.library=true 13 | -------------------------------------------------------------------------------- /library/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/library/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /library/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/library/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /library/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/library/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /library/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/library/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /library/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /library/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World, WorldMapActivity! 5 | WorldMap 6 | 7 | -------------------------------------------------------------------------------- /library/src/com/sigseg/android/io/RandomAccessFileInputStream.java: -------------------------------------------------------------------------------- 1 | // *** WARNING *** 2 | // This piece of code is guaranteed incorrect and not doing what you 3 | // expect - it has been deliberately botched to work around bugs in 4 | // other software. 5 | 6 | package com.sigseg.android.io; 7 | 8 | import java.io.File; 9 | import java.io.InputStream; 10 | import java.io.FileNotFoundException; 11 | import java.io.IOException; 12 | import java.io.RandomAccessFile; 13 | 14 | import android.util.Log; 15 | 16 | public class RandomAccessFileInputStream extends InputStream { 17 | 18 | public static int DEFAULT_BUFFER_SIZE = 16 * 1024; 19 | RandomAccessFile fp; 20 | long markPos=-1; 21 | long fileLength = -1; 22 | String TAG="WorldMapActivityRAIFS"; 23 | 24 | public RandomAccessFileInputStream(File file, int bufferSize) 25 | throws FileNotFoundException { 26 | fp = new RandomAccessFile(file, "r"); 27 | try { 28 | fileLength = fp.length(); 29 | } catch(IOException e) { Log.e(TAG, e.getMessage()); } 30 | Log.d(TAG,"opened, len = "+fileLength); 31 | } 32 | 33 | public int available() { 34 | long pos=0; 35 | int res; 36 | try { 37 | pos=fp.getFilePointer(); 38 | } catch(IOException e){ Log.e(TAG, "available "+e.getMessage()); } 39 | res = (int)(fileLength - pos); 40 | Log.d(TAG,"available "+res); 41 | return res; 42 | } 43 | 44 | public RandomAccessFileInputStream(File file) 45 | throws FileNotFoundException { 46 | this(file, DEFAULT_BUFFER_SIZE); 47 | } 48 | 49 | public RandomAccessFileInputStream(String filename) 50 | throws FileNotFoundException { 51 | this(new File(filename), DEFAULT_BUFFER_SIZE); 52 | } 53 | 54 | 55 | @Override 56 | public int read() throws IOException { 57 | int res=fp.read(); 58 | //Log.d(TAG,"read single byte, res "+res); 59 | return res; 60 | } 61 | 62 | @Override 63 | public int read(byte[] b, int off, int len) throws IOException { 64 | int res=fp.read(b, off, len); 65 | // Log.d(TAG,"read bol, res "+res); 66 | return res; 67 | } 68 | 69 | @Override 70 | public int read(byte[] b) throws IOException { 71 | int res=fp.read(b); 72 | //Log.d(TAG,"read buf, res "+res); 73 | return res; 74 | } 75 | 76 | public void close() throws IOException { 77 | fp.close(); 78 | } 79 | 80 | 81 | public int skip(int n) throws IOException { 82 | int res=fp.skipBytes(n); 83 | long pos=fp.getFilePointer(); 84 | Log.d(TAG,"skip "+n+" res "+res+" pos now "+pos); 85 | return res; 86 | } 87 | 88 | public void mark(int readLimit) { 89 | try { 90 | markPos = fp.getFilePointer(); 91 | /* attempted workaround that did not work */ 92 | /* 93 | if (markPos >= fileLength) { 94 | markPos=0; 95 | Log.d(TAG,"mark at EOF requested - setting mark at 0 instead"); 96 | } 97 | */ 98 | } catch (IOException e) { 99 | Log.e(TAG, e.getMessage()); 100 | } 101 | Log.d(TAG,"mark at "+markPos+" readLimit "+readLimit); 102 | } 103 | 104 | public void reset() throws IOException { 105 | long oldpos=fp.getFilePointer(); 106 | //fp.seek(markPos); 107 | // apparently the only things that works is to reset to zero 108 | // regardless of markPos 109 | fp.seek(0); 110 | long pos=fp.getFilePointer(); 111 | Log.d(TAG,"reset oldPos"+oldpos+" to "+markPos+" resulting pos "+pos); 112 | } 113 | 114 | public boolean markSupported() { 115 | return true; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /library/src/com/sigseg/android/map/ImageSurfaceView.java: -------------------------------------------------------------------------------- 1 | package com.sigseg.android.map; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Point; 6 | import android.graphics.PointF; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | import android.view.*; 10 | import android.view.GestureDetector.OnGestureListener; 11 | import android.widget.Scroller; 12 | import com.sigseg.android.view.InputStreamScene; 13 | 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | 17 | public class ImageSurfaceView extends SurfaceView implements SurfaceHolder.Callback, OnGestureListener { 18 | private final static String TAG = ImageSurfaceView.class.getSimpleName(); 19 | 20 | private InputStreamScene scene; 21 | private final Touch touch; 22 | private GestureDetector gestureDectector; 23 | private ScaleGestureDetector scaleGestureDetector; 24 | private long lastScaleTime = 0; 25 | private long SCALE_MOVE_GUARD = 500; // milliseconds after scale to ignore move events 26 | 27 | private DrawThread drawThread; 28 | 29 | //region getters and setters 30 | public void getViewport(Point p){ 31 | scene.getViewport().getOrigin(p); 32 | } 33 | 34 | public void setViewport(Point viewport){ 35 | scene.getViewport().setOrigin(viewport.x, viewport.y); 36 | } 37 | 38 | public void setViewportCenter() { 39 | Point viewportSize = new Point(); 40 | Point sceneSize = scene.getSceneSize(); 41 | scene.getViewport().getSize(viewportSize); 42 | 43 | int x = (sceneSize.x - viewportSize.x) / 2; 44 | int y = (sceneSize.y - viewportSize.y) / 2; 45 | scene.getViewport().setOrigin(x, y); 46 | } 47 | 48 | public void setInputStream(InputStream inputStream) throws IOException { 49 | scene = new InputStreamScene(inputStream); 50 | } 51 | 52 | //endregion 53 | 54 | //region extends SurfaceView 55 | @Override 56 | public boolean onTouchEvent(MotionEvent me) { 57 | boolean consumed = gestureDectector.onTouchEvent(me); 58 | if (consumed) 59 | return true; 60 | scaleGestureDetector.onTouchEvent(me); 61 | switch (me.getAction() & MotionEvent.ACTION_MASK) { 62 | case MotionEvent.ACTION_DOWN: return touch.down(me); 63 | case MotionEvent.ACTION_MOVE: 64 | if (scaleGestureDetector.isInProgress() || System.currentTimeMillis()-lastScaleTime> DOWN_SAMPLE_SHIFT); 70 | int top = (rectOfSample.top>> DOWN_SAMPLE_SHIFT); 71 | int right = left + (rectOfSample.width()>> DOWN_SAMPLE_SHIFT); 72 | int bottom = top + (rectOfSample.height()>> DOWN_SAMPLE_SHIFT); 73 | Rect srcRect = new Rect( left, top, right, bottom ); 74 | Rect identity= new Rect(0,0,c.getWidth(),c.getHeight()); 75 | c.drawBitmap( 76 | sampleBitmap, 77 | srcRect, 78 | identity, 79 | null 80 | ); 81 | // c.drawLine(0L,0L,c.getWidth(),c.getHeight(),red); 82 | } 83 | } 84 | 85 | // @Override 86 | // protected Rect calculateCacheWindow(Rect viewportRect) { 87 | // // Simplest implementation 88 | // return viewportRect; 89 | // } 90 | 91 | private Rect calculatedCacheWindowRect = new Rect(); 92 | @Override 93 | protected Rect calculateCacheWindow(Rect viewportRect) { 94 | long bytesToUse = Runtime.getRuntime().maxMemory() * percent / 100; 95 | Point size = getSceneSize(); 96 | 97 | int vw = viewportRect.width(); 98 | int vh = viewportRect.height(); 99 | 100 | // Calculate the max size of the margins to fit in our memory budget 101 | int tw=0; 102 | int th=0; 103 | int mw = tw; 104 | int mh = th; 105 | while((vw+tw) * (vh+th) * BYTES_PER_PIXEL < bytesToUse){ 106 | mw = tw++; 107 | mh = th++; 108 | } 109 | 110 | // Trim the margins if they're too big. 111 | if (vw+mw > size.x) // viewport width + margin width > width of the image 112 | mw = Math.max(0, size.x-vw); 113 | if (vh+mh > size.y) // viewport height + margin height > height of the image 114 | mh = Math.max(0, size.y-vh); 115 | 116 | // Figure out the left & right based on the margin. We assume our viewportRect 117 | // is <= our size. If that's not the case, then this logic breaks. 118 | int left = viewportRect.left - (mw>>1); 119 | int right = viewportRect.right + (mw>>1); 120 | if (left<0){ 121 | right = right - left; // Add's the overage on the left side back to the right 122 | left = 0; 123 | } 124 | if (right>size.x){ 125 | left = left - (right-size.x); // Adds overage on right side back to left 126 | right = size.x; 127 | } 128 | 129 | // Figure out the top & bottom based on the margin. We assume our viewportRect 130 | // is <= our size. If that's not the case, then this logic breaks. 131 | int top = viewportRect.top - (mh>>1); 132 | int bottom = viewportRect.bottom + (mh>>1); 133 | if (top<0){ 134 | bottom = bottom - top; // Add's the overage on the top back to the bottom 135 | top = 0; 136 | } 137 | if (bottom>size.y){ 138 | top = top - (bottom-size.y); // Adds overage on bottom back to top 139 | bottom = size.y; 140 | } 141 | 142 | // Set the origin based on our new calculated values. 143 | calculatedCacheWindowRect.set(left, top, right, bottom); 144 | if (DEBUG) Log.d(TAG,"new cache.originRect = "+calculatedCacheWindowRect.toShortString()+" size="+size.toString()); 145 | return calculatedCacheWindowRect; 146 | } 147 | 148 | @Override 149 | protected void fillCacheOutOfMemoryError(OutOfMemoryError error) { 150 | if (percent>0) 151 | percent -= 1; 152 | Log.e(TAG,String.format("caught oom -- cache now at %d percent.",percent)); 153 | } 154 | 155 | @Override 156 | protected void drawComplete(Canvas canvas) { 157 | // TODO Auto-generated method stub 158 | 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /library/src/com/sigseg/android/view/Scene.java: -------------------------------------------------------------------------------- 1 | package com.sigseg.android.view; 2 | 3 | import android.graphics.*; 4 | import android.graphics.Bitmap.Config; 5 | import android.os.Debug; 6 | import android.util.Log; 7 | 8 | /* 9 | * +-------------------------------------------------------------------+ 10 | * | | | 11 | * | +------------------------+ | | 12 | * | | | | | 13 | * | | | | | 14 | * | | | | | 15 | * | | Viewport | | | 16 | * | +------------------------+ | | 17 | * | | | 18 | * | | | 19 | * | | | 20 | * | Cache | | 21 | * |----------------------------------------+ | 22 | * | | 23 | * | | 24 | * | | 25 | * | | 26 | * | | 27 | * | Entire bitmap -- too big for memory | 28 | * +-------------------------------------------------------------------+ 29 | */ 30 | /** 31 | * Keeps track of an entire Scene -- a bitmap (or virtual bitmap) that is much too large 32 | * to fit into memory. Clients subclass this class and extend its abstract methods to 33 | * actually return the necessary bitmaps. 34 | */ 35 | public abstract class Scene { 36 | private final String TAG = "Scene"; 37 | 38 | private final static int MINIMUM_PIXELS_IN_VIEW = 50; 39 | 40 | /** The size of the Scene */ 41 | private Point size = new Point(); 42 | /** The viewport */ 43 | private final Viewport viewport = new Viewport(); 44 | /** The cache */ 45 | private final Cache cache = new Cache(); 46 | 47 | //region [gs]etSceneSize 48 | /** Set the size of the scene */ 49 | public void setSceneSize(int width, int height){ 50 | size.set(width, height); 51 | } 52 | /** Returns a Point representing the size of the scene. Don't modify the returned Point! */ 53 | public Point getSceneSize(){ 54 | return size; 55 | } 56 | /** Set the passed-in point to the size of the scene */ 57 | public void getSceneSize(Point point){ 58 | point.set(size.x, size.y); 59 | } 60 | //endregion 61 | 62 | //region getViewport() 63 | public Viewport getViewport(){return viewport;} 64 | //endregion 65 | 66 | //region initialize/start/stop/suspend/invalidate the cache 67 | /** Initializes the cache */ 68 | public void initialize(){ 69 | if (cache.getState()==CacheState.UNINITIALIZED){ 70 | synchronized(cache){ 71 | cache.setState(CacheState.INITIALIZED); 72 | } 73 | } 74 | } 75 | /** Starts the cache thread */ 76 | public void start(){ 77 | cache.start(); 78 | } 79 | /** Stops the cache thread */ 80 | public void stop(){ 81 | cache.stop(); 82 | } 83 | /** 84 | * Suspends or unsuspends the cache thread. This can be 85 | * used to temporarily stop the cache from updating 86 | * during a fling event. 87 | * @param suspend True to suspend the cache. False to unsuspend. 88 | */ 89 | public void setSuspend(boolean suspend){ 90 | if (suspend) { 91 | synchronized(cache){ 92 | cache.setState(CacheState.SUSPEND); 93 | } 94 | } else { 95 | if (cache.getState()==CacheState.SUSPEND) { 96 | synchronized(cache){ 97 | cache.setState(CacheState.INITIALIZED); 98 | } 99 | } 100 | } 101 | } 102 | /** Invalidate the cache. This causes it to refill */ 103 | @SuppressWarnings("unused") 104 | public void invalidate(){ 105 | cache.invalidate(); 106 | } 107 | //endregion 108 | 109 | //region void draw(Canvas c) 110 | /** 111 | * Draw the scene to the canvas. This operation fills the canvas with 112 | * the bitmap referenced by the viewport's location within the Scene. 113 | * If the cache already has the data (and is not suspended), then the 114 | * high resolution bitmap from the cache is used. If it's not available, 115 | * then the lower resolution bitmap from the sample is used. 116 | */ 117 | public void draw(Canvas c){ 118 | viewport.draw(c); 119 | } 120 | //endregion 121 | 122 | //region protected abstract 123 | /** 124 | * This method must return a high resolution Bitmap that the Scene 125 | * will use to fill out the viewport bitmap upon request. This bitmap 126 | * is normally larger than the viewport so that the viewport can be 127 | * scrolled without having to refresh the cache. This method runs 128 | * on a thread other than the UI thread, and it is not under a lock, so 129 | * it is expected that this method can run for a long time (seconds?). 130 | * @param rectOfCache The Rect representing the area of the Scene that 131 | * the Scene wants cached. 132 | * @return the Bitmap representing the requested area of the larger bitmap 133 | */ 134 | protected abstract Bitmap fillCache(Rect rectOfCache); 135 | /** 136 | * The memory allocation you just did in fillCache caused an OutOfMemoryError. 137 | * You can attempt to recover. Experience shows that when we get an 138 | * OutOfMemoryError, we're pretty hosed and are going down. For instance, if 139 | * we're trying to decode a bitmap region with 140 | * {@link android.graphics.BitmapRegionDecoder} and we run out of memory, 141 | * we're going to die somewhere in the C code with a SIGSEGV. 142 | * @param error The OutOfMemoryError exception data 143 | */ 144 | protected abstract void fillCacheOutOfMemoryError( OutOfMemoryError error ); 145 | /** 146 | * Calculate the Rect of the cache's window based on the current viewportRect. 147 | * The returned Rect must at least contain the viewportRect, but it can be 148 | * larger if the system believes a bitmap of the returned size will fit into 149 | * memory. This function must be fast as it happens while the cache lock is held. 150 | * @param viewportRect The returned must be able to contain this Rect 151 | * @return The Rect that will be used to fill the cache 152 | */ 153 | protected abstract Rect calculateCacheWindow(Rect viewportRect); 154 | /** 155 | * This method fills the passed-in bitmap with sample data. This function must 156 | * return as fast as possible so it shouldn't have to do any IO at all -- the 157 | * quality of the user experience rests on the speed of this function. 158 | * @param bitmap The Bitmap to fill 159 | * @param rectOfSample Rectangle within the Scene that this bitmap represents. 160 | */ 161 | protected abstract void drawSampleRectIntoBitmap(Bitmap bitmap, Rect rectOfSample); 162 | /** 163 | * The Cache is done drawing the bitmap -- time to add the finishing touches 164 | * @param canvas a canvas on which to draw 165 | */ 166 | protected abstract void drawComplete(Canvas canvas); 167 | //endregion 168 | 169 | //region class Viewport 170 | 171 | public class Viewport { 172 | /** The bitmap of the current viewport */ 173 | Bitmap bitmap = null; 174 | /** A Rect that defines where the Viewport is within the scene */ 175 | final Rect window = new Rect(0,0,0,0); 176 | float zoom = 1.0f; 177 | 178 | public void setOrigin(int x, int y){ 179 | synchronized(this){ 180 | int w = window.width(); 181 | int h = window.height(); 182 | 183 | // check bounds 184 | if (x < 0) 185 | x = 0; 186 | 187 | if (y < 0) 188 | y = 0; 189 | 190 | if (x + w > size.x) 191 | x = size.x - w; 192 | 193 | if (y + h > size.y) 194 | y = size.y - h; 195 | 196 | window.set(x, y, x+w, y+h); 197 | } 198 | } 199 | public void setSize( int w, int h ){ 200 | synchronized (this) { 201 | if (bitmap !=null){ 202 | bitmap.recycle(); 203 | bitmap = null; 204 | } 205 | bitmap = Bitmap.createBitmap(w, h, Config.RGB_565); 206 | window.set( 207 | window.left, 208 | window.top, 209 | window.left + w, 210 | window.top + h); 211 | } 212 | } 213 | public void getOrigin(Point p){ 214 | synchronized (this) { 215 | p.set(window.left, window.top); 216 | } 217 | } 218 | public void getSize(Point p){ 219 | synchronized (this) { 220 | p.x = window.width(); 221 | p.y = window.height(); 222 | } 223 | } 224 | public void getPhysicalSize(Point p){ 225 | synchronized (this){ 226 | p.x = getPhysicalWidth(); 227 | p.y = getPhysicalHeight(); 228 | } 229 | } 230 | public int getPhysicalWidth(){ 231 | return bitmap.getWidth(); 232 | } 233 | public int getPhysicalHeight(){ 234 | return bitmap.getHeight(); 235 | } 236 | public float getZoom(){ 237 | return zoom; 238 | } 239 | public void zoom(float factor, PointF screenFocus){ 240 | if (factor!=1.0){ 241 | 242 | PointF screenSize = new PointF(bitmap.getWidth(),bitmap.getHeight()); 243 | PointF sceneSize = new PointF(getSceneSize()); 244 | float screenWidthToHeight = screenSize.x / screenSize.y; 245 | float screenHeightToWidth = screenSize.y / screenSize.x; 246 | synchronized (this){ 247 | float newZoom = zoom * factor; 248 | RectF w1 = new RectF(window); 249 | RectF w2 = new RectF(); 250 | PointF sceneFocus = new PointF( 251 | w1.left + (screenFocus.x/screenSize.x)*w1.width(), 252 | w1.top + (screenFocus.y/screenSize.y)*w1.height() 253 | ); 254 | float w2Width = getPhysicalWidth() * newZoom; 255 | if (w2Width > sceneSize.x){ 256 | w2Width = sceneSize.x; 257 | newZoom = w2Width / getPhysicalWidth(); 258 | } 259 | if (w2Width < MINIMUM_PIXELS_IN_VIEW){ 260 | w2Width = MINIMUM_PIXELS_IN_VIEW; 261 | newZoom = w2Width / getPhysicalWidth(); 262 | } 263 | float w2Height = w2Width * screenHeightToWidth; 264 | if (w2Height > sceneSize.y){ 265 | w2Height = sceneSize.y; 266 | w2Width = w2Height * screenWidthToHeight; 267 | newZoom = w2Width / getPhysicalWidth(); 268 | } 269 | if (w2Height < MINIMUM_PIXELS_IN_VIEW){ 270 | w2Height = MINIMUM_PIXELS_IN_VIEW; 271 | w2Width = w2Height * screenWidthToHeight; 272 | newZoom = w2Width / getPhysicalWidth(); 273 | } 274 | w2.left = sceneFocus.x - ((screenFocus.x/screenSize.x) * w2Width); 275 | w2.top = sceneFocus.y - ((screenFocus.y/screenSize.y) * w2Height); 276 | if (w2.left<0) 277 | w2.left=0; 278 | if (w2.top<0) 279 | w2.top=0; 280 | w2.right = w2.left+w2Width; 281 | w2.bottom= w2.top+w2Height; 282 | if (w2.right>sceneSize.x){ 283 | w2.right=sceneSize.x; 284 | w2.left=w2.right-w2Width; 285 | } 286 | if (w2.bottom>sceneSize.y){ 287 | w2.bottom=sceneSize.y; 288 | w2.top=w2.bottom-w2Height; 289 | } 290 | window.set((int)w2.left,(int)w2.top,(int)w2.right,(int)w2.bottom); 291 | zoom = newZoom; 292 | // Log.d(TAG,String.format( 293 | // "f=%.2f, z=%.2f, scrf(%.0f,%.0f), scnf(%.0f,%.0f) w1s(%.0f,%.0f) w2s(%.0f,%.0f) w1(%.0f,%.0f,%.0f,%.0f) w2(%.0f,%.0f,%.0f,%.0f)", 294 | // factor, 295 | // zoom, 296 | // screenFocus.x, 297 | // screenFocus.y, 298 | // sceneFocus.x, 299 | // sceneFocus.y, 300 | // w1.width(),w1.height(), 301 | // w2Width, w2Height, 302 | // w1.left,w1.top,w1.right,w1.bottom, 303 | // w2.left,w2.top,w2.right,w2.bottom 304 | // )); 305 | } 306 | } 307 | } 308 | void draw(Canvas c){ 309 | cache.update(this); 310 | synchronized (this){ 311 | if (c!=null && bitmap !=null){ 312 | c.drawBitmap(bitmap, 0F, 0F, null); 313 | drawComplete(c); 314 | } 315 | } 316 | } 317 | } 318 | //endregion 319 | 320 | //region class Cache 321 | 322 | private enum CacheState {UNINITIALIZED,INITIALIZED,START_UPDATE,IN_UPDATE,READY,SUSPEND} 323 | /** 324 | * Keep track of the cached bitmap 325 | */ 326 | private class Cache { 327 | /** A Rect that defines where the Cache is within the scene */ 328 | final Rect window = new Rect(0,0,0,0); 329 | /** The bitmap of the current cache */ 330 | Bitmap bitmapRef = null; 331 | CacheState state = CacheState.UNINITIALIZED; 332 | 333 | void setState(CacheState newState){ 334 | if (Debug.isDebuggerConnected()) 335 | Log.i(TAG,String.format("cacheState old=%s new=%s",state.toString(),newState.toString())); 336 | state = newState; 337 | } 338 | CacheState getState(){ return state; } 339 | 340 | /** Our load from disk thread */ 341 | CacheThread cacheThread; 342 | 343 | void start(){ 344 | if (cacheThread!=null){ 345 | cacheThread.setRunning(false); 346 | cacheThread.interrupt(); 347 | cacheThread = null; 348 | } 349 | cacheThread = new CacheThread(this); 350 | cacheThread.setName("cacheThread"); 351 | cacheThread.start(); 352 | } 353 | 354 | void stop(){ 355 | cacheThread.running = false; 356 | cacheThread.interrupt(); 357 | 358 | boolean retry = true; 359 | while (retry) { 360 | try { 361 | cacheThread.join(); 362 | retry = false; 363 | } catch (InterruptedException e) { 364 | // we will try it again and again... 365 | } 366 | } 367 | cacheThread = null; 368 | } 369 | void invalidate(){ 370 | synchronized(this){ 371 | setState(CacheState.INITIALIZED); 372 | cacheThread.interrupt(); 373 | } 374 | } 375 | 376 | /** Fill the bitmap with the part of the scene referenced by the viewport Rect */ 377 | void update(Viewport viewport){ 378 | Bitmap bitmap = null; // If this is null at the bottom, then load from the sample 379 | synchronized(this){ 380 | switch(getState()){ 381 | case UNINITIALIZED: 382 | // nothing can be done -- should never get here 383 | return; 384 | case INITIALIZED: 385 | // time to cache some data 386 | setState(CacheState.START_UPDATE); 387 | cacheThread.interrupt(); 388 | break; 389 | case START_UPDATE: 390 | // I already told the thread to start 391 | break; 392 | case IN_UPDATE: 393 | // Already reading some data, just use the sample 394 | break; 395 | case SUSPEND: 396 | // Loading from cache suspended. 397 | break; 398 | case READY: 399 | // I have some data to show 400 | if (bitmapRef==null){ 401 | // Start the cache off right 402 | if (Debug.isDebuggerConnected()) 403 | Log.d(TAG,"bitmapRef is null"); 404 | setState(CacheState.START_UPDATE); 405 | cacheThread.interrupt(); 406 | } else if (!window.contains(viewport.window)){ 407 | if (Debug.isDebuggerConnected()) 408 | Log.d(TAG,"viewport not in cache"); 409 | setState(CacheState.START_UPDATE); 410 | cacheThread.interrupt(); 411 | } else { 412 | // Happy case -- the cache already contains the Viewport 413 | bitmap = bitmapRef; 414 | } 415 | break; 416 | } 417 | } 418 | if (bitmap==null) 419 | loadSampleIntoViewport(); 420 | else 421 | loadBitmapIntoViewport(bitmap); 422 | } 423 | 424 | void loadBitmapIntoViewport(Bitmap bitmap){ 425 | if (bitmap!=null){ 426 | synchronized(viewport){ 427 | int left = viewport.window.left - window.left; 428 | int top = viewport.window.top - window.top; 429 | int right = left + viewport.window.width(); 430 | int bottom = top + viewport.window.height(); 431 | viewport.getPhysicalSize(dstSize); 432 | srcRect.set( left, top, right, bottom ); 433 | dstRect.set(0, 0, dstSize.x, dstSize.y); 434 | Canvas c = new Canvas(viewport.bitmap); 435 | c.drawColor(Color.BLACK); 436 | c.drawBitmap( 437 | bitmap, 438 | srcRect, 439 | dstRect, 440 | null); 441 | // try { 442 | // FileOutputStream fos = new FileOutputStream("/sdcard/viewport.png"); 443 | // viewport.bitmap.compress(Bitmap.CompressFormat.PNG, 99, fos); 444 | // Thread.sleep(1000); 445 | // } catch (Exception e){ 446 | // System.out.print(e.getMessage()); 447 | // } 448 | } 449 | } 450 | } 451 | final Rect srcRect = new Rect(0,0,0,0); 452 | final Rect dstRect = new Rect(0,0,0,0); 453 | final Point dstSize = new Point(); 454 | 455 | void loadSampleIntoViewport(){ 456 | if (getState()!=CacheState.UNINITIALIZED){ 457 | synchronized(viewport){ 458 | drawSampleRectIntoBitmap( 459 | viewport.bitmap, 460 | viewport.window 461 | ); 462 | } 463 | } 464 | } 465 | } 466 | //endregion 467 | 468 | //region class CacheThread 469 | /** 470 | *

The CacheThread's job is to wait until the {@link Cache#state} is 471 | * {@link CacheState#START_UPDATE} and then update the {@link Cache} given 472 | * the current {@link Viewport#window}. It does not want to hold the cache 473 | * lock during the call to {@link Scene#fillCache(Rect)} because the call 474 | * can take a long time. If we hold the lock, the user experience is very 475 | * jumpy.

476 | *

The CacheThread and the {@link Cache} work hand in hand, both using the 477 | * cache itself to synchronize on and using the {@link Cache#state}. 478 | * The {@link Cache} is free to update any part of the cache object as long 479 | * as it holds the lock. The CacheThread is careful to make sure that it is 480 | * the {@link Cache#state} is {@link CacheState#IN_UPDATE} as it updates 481 | * the {@link Cache}. It locks and unlocks the cache all along the way, but 482 | * makes sure that the cache is not locked when it calls 483 | * {@link Scene#fillCache(Rect)}. 484 | */ 485 | class CacheThread extends Thread { 486 | final Cache cache; 487 | boolean running = false; 488 | void setRunning(boolean value){ running = value; } 489 | 490 | CacheThread(Cache cache){ this.cache = cache; } 491 | 492 | @Override 493 | public void run() { 494 | running=true; 495 | Rect viewportRect = new Rect(0,0,0,0); 496 | while(running){ 497 | while(running && cache.getState()!=CacheState.START_UPDATE) 498 | try { 499 | // Sleep until we have something to do 500 | Thread.sleep(Integer.MAX_VALUE); 501 | } catch (InterruptedException ignored) {} 502 | if (!running) 503 | return; 504 | long start = System.currentTimeMillis(); 505 | boolean cont = false; 506 | synchronized (cache) { 507 | if (cache.getState()==CacheState.START_UPDATE){ 508 | cache.setState(CacheState.IN_UPDATE); 509 | cache.bitmapRef = null; 510 | cont = true; 511 | } 512 | } 513 | if (cont){ 514 | synchronized(viewport){ 515 | viewportRect.set(viewport.window); 516 | } 517 | synchronized (cache) { 518 | if (cache.getState()==CacheState.IN_UPDATE) 519 | //cache.setWindowRect(viewportRect); 520 | cache.window.set(calculateCacheWindow(viewportRect)); 521 | else 522 | cont = false; 523 | } 524 | if (cont){ 525 | try{ 526 | Bitmap bitmap = fillCache(cache.window); 527 | if (bitmap!=null){ 528 | synchronized (cache){ 529 | if (cache.getState()==CacheState.IN_UPDATE){ 530 | cache.bitmapRef = bitmap; 531 | cache.setState(CacheState.READY); 532 | } else { 533 | Log.w(TAG,"fillCache operation aborted"); 534 | } 535 | } 536 | } 537 | long done = System.currentTimeMillis(); 538 | if (Debug.isDebuggerConnected()) 539 | Log.d(TAG,String.format("fillCache in %dms",done-start)); 540 | } catch (OutOfMemoryError e){ 541 | Log.d(TAG,"CacheThread out of memory"); 542 | /* 543 | * Attempt to recover. Experience shows that if we 544 | * do get an OutOfMemoryError, we're pretty hosed and are going down. 545 | */ 546 | synchronized (cache){ 547 | fillCacheOutOfMemoryError(e); 548 | if (cache.getState()==CacheState.IN_UPDATE){ 549 | cache.setState(CacheState.START_UPDATE); 550 | } 551 | } 552 | } 553 | } 554 | } 555 | } 556 | } 557 | } 558 | //endregion 559 | } 560 | -------------------------------------------------------------------------------- /v3/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | -------------------------------------------------------------------------------- /v3/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /v3/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | namespace 'com.sigseg.android.worldmap' 8 | compileSdk 34 9 | 10 | defaultConfig { 11 | applicationId "com.sigseg.android.worldmap" 12 | minSdkVersion 24 13 | targetSdkVersion 34 14 | versionCode 12 15 | versionName "3.1" 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | kotlinOptions { 29 | jvmTarget = "1.8" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /v3/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /v3/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /v3/app/src/main/assets/world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/app/src/main/assets/world.jpg -------------------------------------------------------------------------------- /v3/app/src/main/java/com/sigseg/android/map/ImageSurfaceView.java: -------------------------------------------------------------------------------- 1 | package com.sigseg.android.map; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Point; 6 | import android.graphics.PointF; 7 | import android.util.AttributeSet; 8 | import android.util.Log; 9 | import android.view.GestureDetector; 10 | import android.view.GestureDetector.OnGestureListener; 11 | import android.view.MotionEvent; 12 | import android.view.ScaleGestureDetector; 13 | import android.view.SurfaceHolder; 14 | import android.view.SurfaceView; 15 | 16 | import com.sigseg.android.view.InputStreamScene; 17 | 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | 21 | public class ImageSurfaceView extends SurfaceView implements SurfaceHolder.Callback, OnGestureListener { 22 | private final static String TAG = ImageSurfaceView.class.getSimpleName(); 23 | 24 | private InputStreamScene scene; 25 | private final TouchController touch; 26 | private GestureDetector gestureDectector; 27 | private ScaleGestureDetector scaleGestureDetector; 28 | private long lastScaleTime = 0; 29 | private long SCALE_MOVE_GUARD = 500; // milliseconds after scale to ignore move events 30 | 31 | private DrawThread drawThread; 32 | 33 | //region getters and setters 34 | public void getViewport(Point p){ 35 | scene.getViewport().getOrigin(p); 36 | } 37 | 38 | public void setViewport(Point viewport){ 39 | scene.getViewport().setOrigin(viewport.x, viewport.y); 40 | } 41 | 42 | public void setViewportCenter() { 43 | Point viewportSize = new Point(); 44 | Point sceneSize = scene.getSceneSize(); 45 | scene.getViewport().getSize(viewportSize); 46 | 47 | int x = (sceneSize.x - viewportSize.x) / 2; 48 | int y = (sceneSize.y - viewportSize.y) / 2; 49 | scene.getViewport().setOrigin(x, y); 50 | } 51 | 52 | public void setInputStream(InputStream inputStream) throws IOException { 53 | scene = new InputStreamScene(inputStream); 54 | } 55 | 56 | //endregion 57 | 58 | //region extends SurfaceView 59 | @Override 60 | public boolean onTouchEvent(MotionEvent me) { 61 | boolean consumed = gestureDectector.onTouchEvent(me); 62 | if (consumed) 63 | return true; 64 | scaleGestureDetector.onTouchEvent(me); 65 | switch (me.getAction() & MotionEvent.ACTION_MASK) { 66 | case MotionEvent.ACTION_DOWN: return touch.down(me); 67 | case MotionEvent.ACTION_MOVE: 68 | if (scaleGestureDetector.isInProgress() || System.currentTimeMillis()-lastScaleTimescene, this::invalidate); 82 | init(context); 83 | } 84 | 85 | public ImageSurfaceView(Context context, AttributeSet attrs, int defStyle) { 86 | super(context, attrs, defStyle); 87 | touch = new TouchController(context, ()->scene, this::invalidate); 88 | init(context); 89 | } 90 | 91 | public ImageSurfaceView(Context context, AttributeSet attrs) { 92 | super(context, attrs); 93 | touch = new TouchController(context, ()->scene, this::invalidate); 94 | init(context); 95 | } 96 | 97 | private void init(Context context){ 98 | gestureDectector = new GestureDetector(context,this); 99 | getHolder().addCallback(this); 100 | scaleGestureDetector = new ScaleGestureDetector(context, new ScaleListener()); 101 | } 102 | //endregion 103 | 104 | //region class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener 105 | private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { 106 | private PointF screenFocus = new PointF(); 107 | @Override 108 | public boolean onScale(ScaleGestureDetector detector) { 109 | float scaleFactor = detector.getScaleFactor(); 110 | if (scaleFactor!=0f && scaleFactor!=1.0f){ 111 | scaleFactor = 1/scaleFactor; 112 | screenFocus.set(detector.getFocusX(),detector.getFocusY()); 113 | scene.getViewport().zoom( 114 | scaleFactor, 115 | screenFocus); 116 | invalidate(); 117 | } 118 | lastScaleTime = System.currentTimeMillis(); 119 | return true; 120 | } 121 | } 122 | 123 | //endregion 124 | 125 | 126 | //region implements SurfaceHolder.Callback 127 | @Override 128 | public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 129 | scene.getViewport().setSize(width, height); 130 | Log.d(TAG,String.format("onSizeChanged(w=%d,h=%d)",width,height)); 131 | } 132 | 133 | @Override 134 | public void surfaceCreated(SurfaceHolder holder) { 135 | drawThread = new DrawThread(holder); 136 | drawThread.setName("drawThread"); 137 | drawThread.setRunning(true); 138 | drawThread.start(); 139 | scene.start(); 140 | touch.start(); 141 | } 142 | 143 | @Override 144 | public void surfaceDestroyed(SurfaceHolder holder) { 145 | touch.stop(); 146 | scene.stop(); 147 | drawThread.setRunning(false); 148 | boolean retry = true; 149 | while (retry) { 150 | try { 151 | drawThread.join(); 152 | retry = false; 153 | } catch (InterruptedException e) { 154 | // we will try it again and again... 155 | } 156 | } 157 | } 158 | //endregion 159 | 160 | //region implements OnGestureListener 161 | @Override 162 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { 163 | return touch.fling(velocityX, velocityY); 164 | } 165 | //region the rest are defaults 166 | @Override 167 | public boolean onDown(MotionEvent e) { 168 | return false; 169 | } 170 | 171 | @Override 172 | public void onLongPress(MotionEvent e) { 173 | } 174 | 175 | @Override 176 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { 177 | return false; 178 | } 179 | 180 | @Override 181 | public void onShowPress(MotionEvent e) { 182 | } 183 | 184 | @Override 185 | public boolean onSingleTapUp(MotionEvent e) { 186 | return false; 187 | } 188 | //endregion 189 | 190 | //endregion 191 | 192 | //region class DrawThread 193 | 194 | class DrawThread extends Thread { 195 | private SurfaceHolder surfaceHolder; 196 | 197 | private boolean running = false; 198 | public void setRunning(boolean value){ running = value; } 199 | 200 | public DrawThread(SurfaceHolder surfaceHolder){ 201 | this.surfaceHolder = surfaceHolder; 202 | } 203 | 204 | @Override 205 | public void run() { 206 | Canvas c; 207 | while (running) { 208 | try { 209 | // Don't hog the entire CPU 210 | Thread.sleep(5); 211 | } catch (InterruptedException e) {} 212 | c = null; 213 | try { 214 | c = surfaceHolder.lockCanvas(); 215 | if (c!=null){ 216 | synchronized (surfaceHolder) { 217 | scene.draw(c);// draw it 218 | } 219 | } 220 | } finally { 221 | if (c != null) { 222 | surfaceHolder.unlockCanvasAndPost(c); 223 | } 224 | } 225 | } 226 | } 227 | } 228 | //endregion 229 | 230 | //region class Touch 231 | 232 | //endregion 233 | 234 | } 235 | -------------------------------------------------------------------------------- /v3/app/src/main/java/com/sigseg/android/map/ImageViewerActivity.kt: -------------------------------------------------------------------------------- 1 | package com.sigseg.android.map 2 | 3 | import android.app.Activity 4 | import android.graphics.Point 5 | import android.os.Bundle 6 | import android.view.Window 7 | import android.view.WindowManager 8 | import com.sigseg.android.worldmap.R 9 | 10 | private const val KEY_X = "X" 11 | private const val KEY_Y = "Y" 12 | private const val MAP_FILE = "world.jpg" 13 | 14 | class ImageViewerActivity : Activity() { 15 | private val imageSurfaceView by lazy { findViewById(R.id.worldview) } 16 | 17 | override fun onCreate(bundle: Bundle?) { 18 | super.onCreate(bundle) 19 | requestWindowFeature(Window.FEATURE_NO_TITLE) 20 | window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) 21 | setContentView(R.layout.main) 22 | with(imageSurfaceView) { 23 | setInputStream(assets.open(MAP_FILE)) 24 | post { 25 | val p = bundle?.takeIf { it.containsKey(KEY_X) && it.containsKey(KEY_Y) }?.let { 26 | Point(it.getInt(KEY_X), it.getInt(KEY_Y)) 27 | } 28 | if (p != null) { 29 | imageSurfaceView.setViewport(p) 30 | } else { 31 | imageSurfaceView.setViewportCenter() 32 | } 33 | } 34 | } 35 | } 36 | 37 | override fun onSaveInstanceState(outState: Bundle) { 38 | val p = Point().apply { imageSurfaceView.getViewport(this) } 39 | outState.putInt(KEY_X, p.x) 40 | outState.putInt(KEY_Y, p.y) 41 | super.onSaveInstanceState(outState) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /v3/app/src/main/java/com/sigseg/android/map/TouchController.kt: -------------------------------------------------------------------------------- 1 | package com.sigseg.android.map 2 | 3 | import android.content.Context 4 | import android.graphics.Point 5 | import android.view.MotionEvent 6 | import android.widget.Scroller 7 | import com.sigseg.android.view.Scene 8 | 9 | internal class TouchController( 10 | context: Context, 11 | private val scene: () -> Scene, 12 | private val doInvalidate: Runnable 13 | ) { 14 | private var state = TouchState.UNTOUCHED 15 | 16 | /** Where on the view did we initially touch */ 17 | private val viewDown = Point(0, 0) 18 | 19 | /** What was the coordinates of the viewport origin? */ 20 | private val viewportOriginAtDown = Point(0, 0) 21 | private val scroller = Scroller(context) 22 | private var touchThread: TouchThread? = null 23 | 24 | fun start() { 25 | touchThread = TouchThread(this).apply { 26 | start() 27 | } 28 | } 29 | 30 | fun stop() { 31 | val thread = touchThread 32 | if (thread!= null) { 33 | thread.stopThread() 34 | var retry = true 35 | while (retry) { 36 | try { 37 | thread.join() 38 | retry = false 39 | } catch (e: InterruptedException) { 40 | // we will try it again and again... 41 | } 42 | } 43 | touchThread = null 44 | } 45 | } 46 | 47 | 48 | fun fling(velocityX: Float, velocityY: Float): Boolean { 49 | val thread = touchThread 50 | if (thread != null ){ 51 | val origin = Point().apply { scene().viewport.getOrigin(this) } 52 | val viewSize = Point().apply { scene().viewport.getSize(this) } 53 | val sceneSize = Point().apply { scene().getSceneSize(this) } 54 | 55 | synchronized(this) { 56 | state = TouchState.START_FLING 57 | scene().setSuspend(true) 58 | scroller.fling( 59 | origin.x, 60 | origin.y, -velocityX.toInt(), -velocityY.toInt(), 61 | 0, 62 | sceneSize.x - viewSize.x, 63 | 0, 64 | sceneSize.y - viewSize.y 65 | ) 66 | thread.interrupt() 67 | } 68 | } 69 | return true 70 | } 71 | 72 | fun down(event: MotionEvent): Boolean { 73 | scene().setSuspend(false) // If we were suspended because of a fling 74 | synchronized(this) { 75 | state = TouchState.IN_TOUCH 76 | viewDown.x = event.x.toInt() 77 | viewDown.y = event.y.toInt() 78 | val p = Point() 79 | scene().viewport.getOrigin(p) 80 | viewportOriginAtDown[p.x] = p.y 81 | } 82 | return true 83 | } 84 | 85 | fun move(event: MotionEvent): Boolean { 86 | if (state == TouchState.IN_TOUCH) { 87 | val zoom = scene().viewport.zoom 88 | val deltaX = zoom * (event.x - viewDown.x) 89 | val deltaY = zoom * (event.y - viewDown.y) 90 | scene().viewport.setOrigin( 91 | (viewportOriginAtDown.x - deltaX).toInt(), 92 | (viewportOriginAtDown.y - deltaY).toInt() 93 | ) 94 | doInvalidate.run() 95 | } 96 | return true 97 | } 98 | 99 | fun up(): Boolean { 100 | if (state == TouchState.IN_TOUCH) { 101 | state = TouchState.UNTOUCHED 102 | } 103 | return true 104 | } 105 | 106 | fun cancel(): Boolean { 107 | if (state == TouchState.IN_TOUCH) { 108 | state = TouchState.UNTOUCHED 109 | } 110 | return true 111 | } 112 | 113 | fun inFling() = state in setOf(TouchState.START_FLING, TouchState.IN_FLING) 114 | 115 | fun startFling() { 116 | synchronized(this) { 117 | if (state == TouchState.START_FLING) { 118 | state = TouchState.IN_FLING 119 | } 120 | } 121 | if (state == TouchState.IN_FLING) { 122 | scroller.computeScrollOffset() 123 | scene().viewport.setOrigin(scroller.currX, scroller.currY) 124 | if (scroller.isFinished) { 125 | scene().setSuspend(false) 126 | synchronized(this) { 127 | state = TouchState.UNTOUCHED 128 | try { 129 | Thread.sleep(5) 130 | } catch (e: InterruptedException) { 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /v3/app/src/main/java/com/sigseg/android/map/TouchState.java: -------------------------------------------------------------------------------- 1 | package com.sigseg.android.map; 2 | 3 | enum TouchState {UNTOUCHED, IN_TOUCH, START_FLING, IN_FLING} 4 | -------------------------------------------------------------------------------- /v3/app/src/main/java/com/sigseg/android/map/TouchThread.kt: -------------------------------------------------------------------------------- 1 | package com.sigseg.android.map 2 | 3 | internal class TouchThread( 4 | private val touch: TouchController 5 | ) : Thread() { 6 | private var running = false 7 | 8 | init { 9 | name = "TouchThread" 10 | } 11 | 12 | override fun start() { 13 | running = true 14 | super.start() 15 | } 16 | 17 | fun stopThread() { 18 | running = false 19 | interrupt() 20 | } 21 | 22 | override fun run() { 23 | while (running) { 24 | while (!touch.inFling()) { 25 | try { sleep(Long.MAX_VALUE) } catch (_: InterruptedException) { } 26 | if (!running) return 27 | } 28 | touch.startFling() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /v3/app/src/main/java/com/sigseg/android/view/InputStreamScene.java: -------------------------------------------------------------------------------- 1 | package com.sigseg.android.view; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | 6 | import android.graphics.*; 7 | import android.util.Log; 8 | 9 | public class InputStreamScene extends Scene { 10 | private static final String TAG=InputStreamScene.class.getSimpleName(); 11 | 12 | private static final boolean DEBUG = false; 13 | private static final BitmapFactory.Options options = new BitmapFactory.Options(); 14 | 15 | /** What is the downsample size for the sample image? 1=1/2, 2=1/4 3=1/8, etc */ 16 | private static final int DOWN_SAMPLE_SHIFT = 2; 17 | 18 | /** How many bytes does one pixel use? */ 19 | private final int BYTES_PER_PIXEL = 4; 20 | 21 | /** What percent of total memory should we use for the cache? The bigger the cache, 22 | * the longer it takes to read -- 1.2 secs for 25%, 600ms for 10%, 500ms for 5%. 23 | * User experience seems to be best for smaller values. 24 | */ 25 | private int percent = 5; // Above 25 and we get OOMs 26 | 27 | private BitmapRegionDecoder decoder; 28 | private Bitmap sampleBitmap; 29 | 30 | static { 31 | options.inPreferredConfig = Bitmap.Config.RGB_565; 32 | } 33 | 34 | public InputStreamScene(InputStream inputStream) throws IOException { 35 | BitmapFactory.Options tmpOptions = new BitmapFactory.Options(); 36 | 37 | this.decoder = BitmapRegionDecoder.newInstance(inputStream, false); 38 | 39 | // Grab the bounds for the scene dimensions 40 | tmpOptions.inJustDecodeBounds = true; 41 | inputStream.reset(); 42 | BitmapFactory.decodeStream(inputStream, null, tmpOptions); 43 | inputStream.reset(); 44 | setSceneSize(tmpOptions.outWidth, tmpOptions.outHeight); 45 | 46 | // Create the sample image 47 | tmpOptions.inJustDecodeBounds = false; 48 | tmpOptions.inSampleSize = (1<< DOWN_SAMPLE_SHIFT); 49 | sampleBitmap = BitmapFactory.decodeStream(inputStream, null, tmpOptions); 50 | 51 | initialize(); 52 | } 53 | 54 | @Override 55 | protected Bitmap fillCache(Rect origin) { 56 | Bitmap bitmap = null; 57 | if (decoder!=null) 58 | bitmap = decoder.decodeRegion( origin, options ); 59 | return bitmap; 60 | } 61 | 62 | private static Paint red = new Paint(); 63 | static{ 64 | red.setColor(Color.RED); 65 | red.setStrokeWidth(5L); 66 | } 67 | @Override 68 | protected void drawSampleRectIntoBitmap(Bitmap bitmap, Rect rectOfSample) { 69 | if (bitmap!=null){ 70 | Canvas c = new Canvas(bitmap); 71 | int left = (rectOfSample.left>> DOWN_SAMPLE_SHIFT); 72 | int top = (rectOfSample.top>> DOWN_SAMPLE_SHIFT); 73 | int right = left + (rectOfSample.width()>> DOWN_SAMPLE_SHIFT); 74 | int bottom = top + (rectOfSample.height()>> DOWN_SAMPLE_SHIFT); 75 | Rect srcRect = new Rect( left, top, right, bottom ); 76 | Rect identity= new Rect(0,0,c.getWidth(),c.getHeight()); 77 | c.drawBitmap( 78 | sampleBitmap, 79 | srcRect, 80 | identity, 81 | null 82 | ); 83 | // c.drawLine(0L,0L,c.getWidth(),c.getHeight(),red); 84 | } 85 | } 86 | 87 | // @Override 88 | // protected Rect calculateCacheWindow(Rect viewportRect) { 89 | // // Simplest implementation 90 | // return viewportRect; 91 | // } 92 | 93 | private Rect calculatedCacheWindowRect = new Rect(); 94 | @Override 95 | protected Rect calculateCacheWindow(Rect viewportRect) { 96 | long bytesToUse = Runtime.getRuntime().maxMemory() * percent / 100; 97 | Point size = getSceneSize(); 98 | 99 | int vw = viewportRect.width(); 100 | int vh = viewportRect.height(); 101 | 102 | // Calculate the max size of the margins to fit in our memory budget 103 | int tw=0; 104 | int th=0; 105 | int mw = tw; 106 | int mh = th; 107 | while((vw+tw) * (vh+th) * BYTES_PER_PIXEL < bytesToUse){ 108 | mw = tw++; 109 | mh = th++; 110 | } 111 | 112 | // Trim the margins if they're too big. 113 | if (vw+mw > size.x) // viewport width + margin width > width of the image 114 | mw = Math.max(0, size.x-vw); 115 | if (vh+mh > size.y) // viewport height + margin height > height of the image 116 | mh = Math.max(0, size.y-vh); 117 | 118 | // Figure out the left & right based on the margin. We assume our viewportRect 119 | // is <= our size. If that's not the case, then this logic breaks. 120 | int left = viewportRect.left - (mw>>1); 121 | int right = viewportRect.right + (mw>>1); 122 | if (left<0){ 123 | right = right - left; // Add's the overage on the left side back to the right 124 | left = 0; 125 | } 126 | if (right>size.x){ 127 | left = left - (right-size.x); // Adds overage on right side back to left 128 | right = size.x; 129 | } 130 | 131 | // Figure out the top & bottom based on the margin. We assume our viewportRect 132 | // is <= our size. If that's not the case, then this logic breaks. 133 | int top = viewportRect.top - (mh>>1); 134 | int bottom = viewportRect.bottom + (mh>>1); 135 | if (top<0){ 136 | bottom = bottom - top; // Add's the overage on the top back to the bottom 137 | top = 0; 138 | } 139 | if (bottom>size.y){ 140 | top = top - (bottom-size.y); // Adds overage on bottom back to top 141 | bottom = size.y; 142 | } 143 | 144 | // Set the origin based on our new calculated values. 145 | calculatedCacheWindowRect.set(left, top, right, bottom); 146 | if (DEBUG) Log.d(TAG,"new cache.originRect = "+calculatedCacheWindowRect.toShortString()+" size="+size.toString()); 147 | return calculatedCacheWindowRect; 148 | } 149 | 150 | @Override 151 | protected void fillCacheOutOfMemoryError(OutOfMemoryError error) { 152 | if (percent>0) 153 | percent -= 1; 154 | Log.e(TAG,String.format("caught oom -- cache now at %d percent.",percent)); 155 | } 156 | 157 | @Override 158 | protected void drawComplete(Canvas canvas) { 159 | // TODO Auto-generated method stub 160 | 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /v3/app/src/main/java/com/sigseg/android/view/Scene.java: -------------------------------------------------------------------------------- 1 | package com.sigseg.android.view; 2 | 3 | import android.graphics.*; 4 | import android.graphics.Bitmap.Config; 5 | import android.os.Debug; 6 | import android.util.Log; 7 | 8 | /* 9 | * +-------------------------------------------------------------------+ 10 | * | | | 11 | * | +------------------------+ | | 12 | * | | | | | 13 | * | | | | | 14 | * | | | | | 15 | * | | Viewport | | | 16 | * | +------------------------+ | | 17 | * | | | 18 | * | | | 19 | * | | | 20 | * | Cache | | 21 | * |----------------------------------------+ | 22 | * | | 23 | * | | 24 | * | | 25 | * | | 26 | * | | 27 | * | Entire bitmap -- too big for memory | 28 | * +-------------------------------------------------------------------+ 29 | */ 30 | /** 31 | * Keeps track of an entire Scene -- a bitmap (or virtual bitmap) that is much too large 32 | * to fit into memory. Clients subclass this class and extend its abstract methods to 33 | * actually return the necessary bitmaps. 34 | */ 35 | public abstract class Scene { 36 | private final String TAG = "Scene"; 37 | 38 | private final static int MINIMUM_PIXELS_IN_VIEW = 50; 39 | 40 | /** The size of the Scene */ 41 | private Point size = new Point(); 42 | /** The viewport */ 43 | private final Viewport viewport = new Viewport(); 44 | /** The cache */ 45 | private final Cache cache = new Cache(); 46 | 47 | //region [gs]etSceneSize 48 | /** Set the size of the scene */ 49 | public void setSceneSize(int width, int height){ 50 | size.set(width, height); 51 | } 52 | /** Returns a Point representing the size of the scene. Don't modify the returned Point! */ 53 | public Point getSceneSize(){ 54 | return size; 55 | } 56 | /** Set the passed-in point to the size of the scene */ 57 | public void getSceneSize(Point point){ 58 | point.set(size.x, size.y); 59 | } 60 | //endregion 61 | 62 | //region getViewport() 63 | public Viewport getViewport(){return viewport;} 64 | //endregion 65 | 66 | //region initialize/start/stop/suspend/invalidate the cache 67 | /** Initializes the cache */ 68 | public void initialize(){ 69 | if (cache.getState()==CacheState.UNINITIALIZED){ 70 | synchronized(cache){ 71 | cache.setState(CacheState.INITIALIZED); 72 | } 73 | } 74 | } 75 | /** Starts the cache thread */ 76 | public void start(){ 77 | cache.start(); 78 | } 79 | /** Stops the cache thread */ 80 | public void stop(){ 81 | cache.stop(); 82 | } 83 | /** 84 | * Suspends or unsuspends the cache thread. This can be 85 | * used to temporarily stop the cache from updating 86 | * during a fling event. 87 | * @param suspend True to suspend the cache. False to unsuspend. 88 | */ 89 | public void setSuspend(boolean suspend){ 90 | if (suspend) { 91 | synchronized(cache){ 92 | cache.setState(CacheState.SUSPEND); 93 | } 94 | } else { 95 | if (cache.getState()==CacheState.SUSPEND) { 96 | synchronized(cache){ 97 | cache.setState(CacheState.INITIALIZED); 98 | } 99 | } 100 | } 101 | } 102 | /** Invalidate the cache. This causes it to refill */ 103 | @SuppressWarnings("unused") 104 | public void invalidate(){ 105 | cache.invalidate(); 106 | } 107 | //endregion 108 | 109 | //region void draw(Canvas c) 110 | /** 111 | * Draw the scene to the canvas. This operation fills the canvas with 112 | * the bitmap referenced by the viewport's location within the Scene. 113 | * If the cache already has the data (and is not suspended), then the 114 | * high resolution bitmap from the cache is used. If it's not available, 115 | * then the lower resolution bitmap from the sample is used. 116 | */ 117 | public void draw(Canvas c){ 118 | viewport.draw(c); 119 | } 120 | //endregion 121 | 122 | //region protected abstract 123 | /** 124 | * This method must return a high resolution Bitmap that the Scene 125 | * will use to fill out the viewport bitmap upon request. This bitmap 126 | * is normally larger than the viewport so that the viewport can be 127 | * scrolled without having to refresh the cache. This method runs 128 | * on a thread other than the UI thread, and it is not under a lock, so 129 | * it is expected that this method can run for a long time (seconds?). 130 | * @param rectOfCache The Rect representing the area of the Scene that 131 | * the Scene wants cached. 132 | * @return the Bitmap representing the requested area of the larger bitmap 133 | */ 134 | protected abstract Bitmap fillCache(Rect rectOfCache); 135 | /** 136 | * The memory allocation you just did in fillCache caused an OutOfMemoryError. 137 | * You can attempt to recover. Experience shows that when we get an 138 | * OutOfMemoryError, we're pretty hosed and are going down. For instance, if 139 | * we're trying to decode a bitmap region with 140 | * {@link android.graphics.BitmapRegionDecoder} and we run out of memory, 141 | * we're going to die somewhere in the C code with a SIGSEGV. 142 | * @param error The OutOfMemoryError exception data 143 | */ 144 | protected abstract void fillCacheOutOfMemoryError( OutOfMemoryError error ); 145 | /** 146 | * Calculate the Rect of the cache's window based on the current viewportRect. 147 | * The returned Rect must at least contain the viewportRect, but it can be 148 | * larger if the system believes a bitmap of the returned size will fit into 149 | * memory. This function must be fast as it happens while the cache lock is held. 150 | * @param viewportRect The returned must be able to contain this Rect 151 | * @return The Rect that will be used to fill the cache 152 | */ 153 | protected abstract Rect calculateCacheWindow(Rect viewportRect); 154 | /** 155 | * This method fills the passed-in bitmap with sample data. This function must 156 | * return as fast as possible so it shouldn't have to do any IO at all -- the 157 | * quality of the user experience rests on the speed of this function. 158 | * @param bitmap The Bitmap to fill 159 | * @param rectOfSample Rectangle within the Scene that this bitmap represents. 160 | */ 161 | protected abstract void drawSampleRectIntoBitmap(Bitmap bitmap, Rect rectOfSample); 162 | /** 163 | * The Cache is done drawing the bitmap -- time to add the finishing touches 164 | * @param canvas a canvas on which to draw 165 | */ 166 | protected abstract void drawComplete(Canvas canvas); 167 | //endregion 168 | 169 | //region class Viewport 170 | 171 | public class Viewport { 172 | /** The bitmap of the current viewport */ 173 | Bitmap bitmap = null; 174 | /** A Rect that defines where the Viewport is within the scene */ 175 | final Rect window = new Rect(0,0,0,0); 176 | float zoom = 1.0f; 177 | 178 | public void setOrigin(int x, int y){ 179 | synchronized(this){ 180 | int w = window.width(); 181 | int h = window.height(); 182 | 183 | // check bounds 184 | if (x < 0) 185 | x = 0; 186 | 187 | if (y < 0) 188 | y = 0; 189 | 190 | if (x + w > size.x) 191 | x = size.x - w; 192 | 193 | if (y + h > size.y) 194 | y = size.y - h; 195 | 196 | window.set(x, y, x+w, y+h); 197 | } 198 | } 199 | public void setSize( int w, int h ){ 200 | synchronized (this) { 201 | if (bitmap !=null){ 202 | bitmap.recycle(); 203 | bitmap = null; 204 | } 205 | bitmap = Bitmap.createBitmap(w, h, Config.RGB_565); 206 | window.set( 207 | window.left, 208 | window.top, 209 | window.left + w, 210 | window.top + h); 211 | } 212 | } 213 | public void getOrigin(Point p){ 214 | synchronized (this) { 215 | p.set(window.left, window.top); 216 | } 217 | } 218 | public void getSize(Point p){ 219 | synchronized (this) { 220 | p.x = window.width(); 221 | p.y = window.height(); 222 | } 223 | } 224 | public void getPhysicalSize(Point p){ 225 | synchronized (this){ 226 | p.x = getPhysicalWidth(); 227 | p.y = getPhysicalHeight(); 228 | } 229 | } 230 | public int getPhysicalWidth(){ 231 | return bitmap.getWidth(); 232 | } 233 | public int getPhysicalHeight(){ 234 | return bitmap.getHeight(); 235 | } 236 | public float getZoom(){ 237 | return zoom; 238 | } 239 | public void zoom(float factor, PointF screenFocus){ 240 | if (factor!=1.0){ 241 | 242 | PointF screenSize = new PointF(bitmap.getWidth(),bitmap.getHeight()); 243 | PointF sceneSize = new PointF(getSceneSize()); 244 | float screenWidthToHeight = screenSize.x / screenSize.y; 245 | float screenHeightToWidth = screenSize.y / screenSize.x; 246 | synchronized (this){ 247 | float newZoom = zoom * factor; 248 | RectF w1 = new RectF(window); 249 | RectF w2 = new RectF(); 250 | PointF sceneFocus = new PointF( 251 | w1.left + (screenFocus.x/screenSize.x)*w1.width(), 252 | w1.top + (screenFocus.y/screenSize.y)*w1.height() 253 | ); 254 | float w2Width = getPhysicalWidth() * newZoom; 255 | if (w2Width > sceneSize.x){ 256 | w2Width = sceneSize.x; 257 | newZoom = w2Width / getPhysicalWidth(); 258 | } 259 | if (w2Width < MINIMUM_PIXELS_IN_VIEW){ 260 | w2Width = MINIMUM_PIXELS_IN_VIEW; 261 | newZoom = w2Width / getPhysicalWidth(); 262 | } 263 | float w2Height = w2Width * screenHeightToWidth; 264 | if (w2Height > sceneSize.y){ 265 | w2Height = sceneSize.y; 266 | w2Width = w2Height * screenWidthToHeight; 267 | newZoom = w2Width / getPhysicalWidth(); 268 | } 269 | if (w2Height < MINIMUM_PIXELS_IN_VIEW){ 270 | w2Height = MINIMUM_PIXELS_IN_VIEW; 271 | w2Width = w2Height * screenWidthToHeight; 272 | newZoom = w2Width / getPhysicalWidth(); 273 | } 274 | w2.left = sceneFocus.x - ((screenFocus.x/screenSize.x) * w2Width); 275 | w2.top = sceneFocus.y - ((screenFocus.y/screenSize.y) * w2Height); 276 | if (w2.left<0) 277 | w2.left=0; 278 | if (w2.top<0) 279 | w2.top=0; 280 | w2.right = w2.left+w2Width; 281 | w2.bottom= w2.top+w2Height; 282 | if (w2.right>sceneSize.x){ 283 | w2.right=sceneSize.x; 284 | w2.left=w2.right-w2Width; 285 | } 286 | if (w2.bottom>sceneSize.y){ 287 | w2.bottom=sceneSize.y; 288 | w2.top=w2.bottom-w2Height; 289 | } 290 | window.set((int)w2.left,(int)w2.top,(int)w2.right,(int)w2.bottom); 291 | zoom = newZoom; 292 | // Log.d(TAG,String.format( 293 | // "f=%.2f, z=%.2f, scrf(%.0f,%.0f), scnf(%.0f,%.0f) w1s(%.0f,%.0f) w2s(%.0f,%.0f) w1(%.0f,%.0f,%.0f,%.0f) w2(%.0f,%.0f,%.0f,%.0f)", 294 | // factor, 295 | // zoom, 296 | // screenFocus.x, 297 | // screenFocus.y, 298 | // sceneFocus.x, 299 | // sceneFocus.y, 300 | // w1.width(),w1.height(), 301 | // w2Width, w2Height, 302 | // w1.left,w1.top,w1.right,w1.bottom, 303 | // w2.left,w2.top,w2.right,w2.bottom 304 | // )); 305 | } 306 | } 307 | } 308 | void draw(Canvas c){ 309 | cache.update(this); 310 | synchronized (this){ 311 | if (c!=null && bitmap !=null){ 312 | c.drawBitmap(bitmap, 0F, 0F, null); 313 | drawComplete(c); 314 | } 315 | } 316 | } 317 | } 318 | //endregion 319 | 320 | //region class Cache 321 | 322 | private enum CacheState {UNINITIALIZED,INITIALIZED,START_UPDATE,IN_UPDATE,READY,SUSPEND} 323 | /** 324 | * Keep track of the cached bitmap 325 | */ 326 | private class Cache { 327 | /** A Rect that defines where the Cache is within the scene */ 328 | final Rect window = new Rect(0,0,0,0); 329 | /** The bitmap of the current cache */ 330 | Bitmap bitmapRef = null; 331 | CacheState state = CacheState.UNINITIALIZED; 332 | 333 | void setState(CacheState newState){ 334 | if (Debug.isDebuggerConnected()) 335 | Log.i(TAG,String.format("cacheState old=%s new=%s",state.toString(),newState.toString())); 336 | state = newState; 337 | } 338 | CacheState getState(){ return state; } 339 | 340 | /** Our load from disk thread */ 341 | CacheThread cacheThread; 342 | 343 | void start(){ 344 | if (cacheThread!=null){ 345 | cacheThread.setRunning(false); 346 | cacheThread.interrupt(); 347 | cacheThread = null; 348 | } 349 | cacheThread = new CacheThread(this); 350 | cacheThread.setName("cacheThread"); 351 | cacheThread.start(); 352 | } 353 | 354 | void stop(){ 355 | cacheThread.running = false; 356 | cacheThread.interrupt(); 357 | 358 | boolean retry = true; 359 | while (retry) { 360 | try { 361 | cacheThread.join(); 362 | retry = false; 363 | } catch (InterruptedException e) { 364 | // we will try it again and again... 365 | } 366 | } 367 | cacheThread = null; 368 | } 369 | void invalidate(){ 370 | synchronized(this){ 371 | setState(CacheState.INITIALIZED); 372 | cacheThread.interrupt(); 373 | } 374 | } 375 | 376 | /** Fill the bitmap with the part of the scene referenced by the viewport Rect */ 377 | void update(Viewport viewport){ 378 | Bitmap bitmap = null; // If this is null at the bottom, then load from the sample 379 | synchronized(this){ 380 | switch(getState()){ 381 | case UNINITIALIZED: 382 | // nothing can be done -- should never get here 383 | return; 384 | case INITIALIZED: 385 | // time to cache some data 386 | setState(CacheState.START_UPDATE); 387 | cacheThread.interrupt(); 388 | break; 389 | case START_UPDATE: 390 | // I already told the thread to start 391 | break; 392 | case IN_UPDATE: 393 | // Already reading some data, just use the sample 394 | break; 395 | case SUSPEND: 396 | // Loading from cache suspended. 397 | break; 398 | case READY: 399 | // I have some data to show 400 | if (bitmapRef==null){ 401 | // Start the cache off right 402 | if (Debug.isDebuggerConnected()) 403 | Log.d(TAG,"bitmapRef is null"); 404 | setState(CacheState.START_UPDATE); 405 | cacheThread.interrupt(); 406 | } else if (!window.contains(viewport.window)){ 407 | if (Debug.isDebuggerConnected()) 408 | Log.d(TAG,"viewport not in cache"); 409 | setState(CacheState.START_UPDATE); 410 | cacheThread.interrupt(); 411 | } else { 412 | // Happy case -- the cache already contains the Viewport 413 | bitmap = bitmapRef; 414 | } 415 | break; 416 | } 417 | } 418 | if (bitmap==null) 419 | loadSampleIntoViewport(); 420 | else 421 | loadBitmapIntoViewport(bitmap); 422 | } 423 | 424 | void loadBitmapIntoViewport(Bitmap bitmap){ 425 | if (bitmap!=null){ 426 | synchronized(viewport){ 427 | int left = viewport.window.left - window.left; 428 | int top = viewport.window.top - window.top; 429 | int right = left + viewport.window.width(); 430 | int bottom = top + viewport.window.height(); 431 | viewport.getPhysicalSize(dstSize); 432 | srcRect.set( left, top, right, bottom ); 433 | dstRect.set(0, 0, dstSize.x, dstSize.y); 434 | Canvas c = new Canvas(viewport.bitmap); 435 | c.drawBitmap( 436 | bitmap, 437 | srcRect, 438 | dstRect, 439 | null); 440 | // try { 441 | // FileOutputStream fos = new FileOutputStream("/sdcard/viewport.png"); 442 | // viewport.bitmap.compress(Bitmap.CompressFormat.PNG, 99, fos); 443 | // Thread.sleep(1000); 444 | // } catch (Exception e){ 445 | // System.out.print(e.getMessage()); 446 | // } 447 | } 448 | } 449 | } 450 | final Rect srcRect = new Rect(0,0,0,0); 451 | final Rect dstRect = new Rect(0,0,0,0); 452 | final Point dstSize = new Point(); 453 | 454 | void loadSampleIntoViewport(){ 455 | if (getState()!=CacheState.UNINITIALIZED){ 456 | synchronized(viewport){ 457 | drawSampleRectIntoBitmap( 458 | viewport.bitmap, 459 | viewport.window 460 | ); 461 | } 462 | } 463 | } 464 | } 465 | //endregion 466 | 467 | //region class CacheThread 468 | /** 469 | *

The CacheThread's job is to wait until the {@link Cache#state} is 470 | * {@link CacheState#START_UPDATE} and then update the {@link Cache} given 471 | * the current {@link Viewport#window}. It does not want to hold the cache 472 | * lock during the call to {@link Scene#fillCache(Rect)} because the call 473 | * can take a long time. If we hold the lock, the user experience is very 474 | * jumpy.

475 | *

The CacheThread and the {@link Cache} work hand in hand, both using the 476 | * cache itself to synchronize on and using the {@link Cache#state}. 477 | * The {@link Cache} is free to update any part of the cache object as long 478 | * as it holds the lock. The CacheThread is careful to make sure that it is 479 | * the {@link Cache#state} is {@link CacheState#IN_UPDATE} as it updates 480 | * the {@link Cache}. It locks and unlocks the cache all along the way, but 481 | * makes sure that the cache is not locked when it calls 482 | * {@link Scene#fillCache(Rect)}. 483 | */ 484 | class CacheThread extends Thread { 485 | final Cache cache; 486 | boolean running = false; 487 | void setRunning(boolean value){ running = value; } 488 | 489 | CacheThread(Cache cache){ this.cache = cache; } 490 | 491 | @Override 492 | public void run() { 493 | running=true; 494 | Rect viewportRect = new Rect(0,0,0,0); 495 | while(running){ 496 | while(running && cache.getState()!=CacheState.START_UPDATE) 497 | try { 498 | // Sleep until we have something to do 499 | Thread.sleep(Integer.MAX_VALUE); 500 | } catch (InterruptedException ignored) {} 501 | if (!running) 502 | return; 503 | long start = System.currentTimeMillis(); 504 | boolean cont = false; 505 | synchronized (cache) { 506 | if (cache.getState()==CacheState.START_UPDATE){ 507 | cache.setState(CacheState.IN_UPDATE); 508 | cache.bitmapRef = null; 509 | cont = true; 510 | } 511 | } 512 | if (cont){ 513 | synchronized(viewport){ 514 | viewportRect.set(viewport.window); 515 | } 516 | synchronized (cache) { 517 | if (cache.getState()==CacheState.IN_UPDATE) 518 | //cache.setWindowRect(viewportRect); 519 | cache.window.set(calculateCacheWindow(viewportRect)); 520 | else 521 | cont = false; 522 | } 523 | if (cont){ 524 | try{ 525 | Bitmap bitmap = fillCache(cache.window); 526 | if (bitmap!=null){ 527 | synchronized (cache){ 528 | if (cache.getState()==CacheState.IN_UPDATE){ 529 | cache.bitmapRef = bitmap; 530 | cache.setState(CacheState.READY); 531 | } else { 532 | Log.w(TAG,"fillCache operation aborted"); 533 | } 534 | } 535 | } 536 | long done = System.currentTimeMillis(); 537 | if (Debug.isDebuggerConnected()) 538 | Log.d(TAG,String.format("fillCache in %dms",done-start)); 539 | } catch (OutOfMemoryError e){ 540 | Log.d(TAG,"CacheThread out of memory"); 541 | /* 542 | * Attempt to recover. Experience shows that if we 543 | * do get an OutOfMemoryError, we're pretty hosed and are going down. 544 | */ 545 | synchronized (cache){ 546 | fillCacheOutOfMemoryError(e); 547 | if (cache.getState()==CacheState.IN_UPDATE){ 548 | cache.setState(CacheState.START_UPDATE); 549 | } 550 | } 551 | } 552 | } 553 | } 554 | } 555 | } 556 | } 557 | //endregion 558 | } 559 | -------------------------------------------------------------------------------- /v3/app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /v3/app/src/main/res/drawable-ldpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/app/src/main/res/drawable-ldpi/ic_launcher.png -------------------------------------------------------------------------------- /v3/app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /v3/app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /v3/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /v3/app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /v3/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /v3/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | WorldMap 4 | -------------------------------------------------------------------------------- /v3/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /v3/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id 'com.android.application' version '8.2.0' apply false 4 | id 'org.jetbrains.kotlin.android' version '1.9.0' apply false 5 | } -------------------------------------------------------------------------------- /v3/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /v3/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/v3/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /v3/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 28 11:19:31 PDT 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /v3/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /v3/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /v3/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | 16 | include ':app' 17 | rootProject.name='WorldMap' 18 | -------------------------------------------------------------------------------- /worldmap/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /worldmap/art/4973-1209-east-sea.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/art/4973-1209-east-sea.pxm -------------------------------------------------------------------------------- /worldmap/assets/layers/ko/4973-1209-east-sea.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/assets/layers/ko/4973-1209-east-sea.jpg -------------------------------------------------------------------------------- /worldmap/assets/world.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/assets/world.jpg -------------------------------------------------------------------------------- /worldmap/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 49 | 50 | 51 | 52 | 56 | 57 | 69 | 70 | 71 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /worldmap/proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class * extends android.app.backup.BackupAgentHelper 14 | -keep public class * extends android.preference.Preference 15 | -keep public class com.android.vending.licensing.ILicensingService 16 | 17 | -keepclasseswithmembernames class * { 18 | native ; 19 | } 20 | 21 | -keepclasseswithmembers class * { 22 | public (android.content.Context, android.util.AttributeSet); 23 | } 24 | 25 | -keepclasseswithmembers class * { 26 | public (android.content.Context, android.util.AttributeSet, int); 27 | } 28 | 29 | -keepclassmembers class * extends android.app.Activity { 30 | public void *(android.view.View); 31 | } 32 | 33 | -keepclassmembers enum * { 34 | public static **[] values(); 35 | public static ** valueOf(java.lang.String); 36 | } 37 | 38 | -keep class * implements android.os.Parcelable { 39 | public static final android.os.Parcelable$Creator *; 40 | } 41 | -------------------------------------------------------------------------------- /worldmap/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-17 12 | android.library.reference.1=../library 13 | -------------------------------------------------------------------------------- /worldmap/release/01/WorldMap.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/01/WorldMap.apk -------------------------------------------------------------------------------- /worldmap/release/01/icon-512x512.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/01/icon-512x512.jpg -------------------------------------------------------------------------------- /worldmap/release/01/shot01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/01/shot01.png -------------------------------------------------------------------------------- /worldmap/release/01/shot02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/01/shot02.png -------------------------------------------------------------------------------- /worldmap/release/02/WorldMap.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/WorldMap.apk -------------------------------------------------------------------------------- /worldmap/release/02/worldmap-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-36x36.png -------------------------------------------------------------------------------- /worldmap/release/02/worldmap-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-48x48.png -------------------------------------------------------------------------------- /worldmap/release/02/worldmap-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-512x512.png -------------------------------------------------------------------------------- /worldmap/release/02/worldmap-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-72x72.png -------------------------------------------------------------------------------- /worldmap/release/02/worldmap-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-96x96.png -------------------------------------------------------------------------------- /worldmap/release/02/worldmap-icon.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/02/worldmap-icon.xcf -------------------------------------------------------------------------------- /worldmap/release/03/WorldMap.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/03/WorldMap.apk -------------------------------------------------------------------------------- /worldmap/release/04/WorldMap-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/04/WorldMap-release.apk -------------------------------------------------------------------------------- /worldmap/release/05/WorldMap-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/05/WorldMap-release.apk -------------------------------------------------------------------------------- /worldmap/release/10/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnylambada/WorldMap/5e6ffa2cb4e44eb26b3138fb7e4a0aa529e5560e/worldmap/release/10/release/app-release.apk -------------------------------------------------------------------------------- /worldmap/release/10/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkData":{"type":"MAIN","splits":[],"versionCode":10,"versionName":"3.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] --------------------------------------------------------------------------------