package net.osmand.plus.render; import gnu.trove.list.array.TIntArrayList; import gnu.trove.set.TLongSet; import gnu.trove.set.hash.TLongHashSet; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import net.osmand.IProgress; import net.osmand.LogUtil; import net.osmand.binary.BinaryMapDataObject; import net.osmand.binary.BinaryMapIndexReader; import net.osmand.binary.BinaryMapIndexReader.SearchRequest; import net.osmand.binary.BinaryMapIndexReader.TagValuePair; import net.osmand.data.index.IndexConstants; import net.osmand.data.preparation.MapTileDownloader.IMapDownloaderCallback; import net.osmand.osm.MapRenderingTypes; import net.osmand.osm.MapUtils; import net.osmand.osm.MultyPolygon; import net.osmand.plus.OsmandSettings; import net.osmand.plus.R; import net.osmand.plus.RotatedTileBox; import net.osmand.plus.activities.OsmandApplication; import net.osmand.plus.render.OsmandRenderer.RenderingContext; import net.osmand.render.OsmandRenderingRulesParser; import org.apache.commons.logging.Log; import android.content.Context; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.RectF; import android.graphics.Bitmap.Config; import android.os.Handler; import android.os.Looper; import android.widget.Toast; public class MapRenderRepositories { private final static Log log = LogUtil.getLog(MapRenderRepositories.class); private final Context context; private Handler handler; private Map<String, BinaryMapIndexReader> files = new LinkedHashMap<String, BinaryMapIndexReader>(); private OsmandRenderer renderer; // lat/lon box of requested vector data private RectF cObjectsBox = new RectF(); // cached objects in order to render rotation without reloading data from db private List<BinaryMapDataObject> cObjects = new LinkedList<BinaryMapDataObject>(); // currently rendered box (not the same as already rendered) // this box is checked for interrupted process or private RotatedTileBox requestedBox = null; // location of rendered bitmap private RotatedTileBox bmpLocation = null; // already rendered bitmap private Bitmap bmp; private boolean interrupted = false; private RenderingContext currentRenderingContext; private SearchRequest<BinaryMapDataObject> searchRequest; private SharedPreferences prefs; public MapRenderRepositories(Context context){ this.context = context; this.renderer = new OsmandRenderer(context); handler = new Handler(Looper.getMainLooper()); prefs = OsmandSettings.getPrefs(context); } public Context getContext() { return context; } public BinaryMapIndexReader initializeNewResource(final IProgress progress, File file) { long start = System.currentTimeMillis(); if(files.containsKey(file.getAbsolutePath())){ closeConnection(files.get(file.getAbsolutePath()), file.getAbsolutePath()); } RandomAccessFile raf = null; BinaryMapIndexReader reader = null; try { raf = new RandomAccessFile(file, "r"); //$NON-NLS-1$ reader = new BinaryMapIndexReader(raf); if(reader.getVersion() != IndexConstants.BINARY_MAP_VERSION){ return null; } files.put(file.getAbsolutePath(), reader); } catch (IOException e) { log.error("No connection or unsupported version", e); //$NON-NLS-1$ if(raf != null){ try { raf.close(); } catch (IOException e1) { } } return null; } catch (OutOfMemoryError oome) { if(raf != null){ try { raf.close(); } catch (IOException e1) { } } throw oome; } if (log.isDebugEnabled()) { log.debug("Initializing db " + file.getAbsolutePath() + " " + (System.currentTimeMillis() - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } return reader; } public Collection<BinaryMapIndexReader> getVectorData(){ return files.values(); } public RotatedTileBox getBitmapLocation() { return bmpLocation; } protected void closeConnection(BinaryMapIndexReader c, String file){ files.remove(file); try { c.close(); } catch (IOException e) { e.printStackTrace(); } } public void clearAllResources(){ clearCache(); for(String f : new ArrayList<String>(files.keySet())){ closeConnection(files.get(f), f); } } public boolean updateMapIsNeeded(RotatedTileBox box){ if (files.isEmpty() || box == null) { return false; } if(requestedBox == null){ return true; } if(requestedBox.getZoom() != box.getZoom()){ return true; } float deltaRotate = requestedBox.getRotate() - box.getRotate(); if(deltaRotate > 180){ deltaRotate -= 360; } else if(deltaRotate < -180){ deltaRotate += 360; } if(Math.abs(deltaRotate) > 25){ return true; } return !requestedBox.containsTileBox(box); } public boolean isEmpty(){ return files.isEmpty(); } public void interruptLoadingMap(){ interrupted = true; if(currentRenderingContext != null){ currentRenderingContext.interrupted = true; } if(searchRequest != null){ searchRequest.setInterrupted(true); } } private boolean checkWhetherInterrupted(){ if(interrupted || (currentRenderingContext != null && currentRenderingContext.interrupted)){ requestedBox = bmpLocation; return true; } return false; } private boolean loadVectorData(RectF dataBox, final int zoom, final BaseOsmandRender renderingType){ double cBottomLatitude = dataBox.bottom; double cTopLatitude = dataBox.top; double cLeftLongitude = dataBox.left; double cRightLongitude = dataBox.right; log.info(String.format("BLat=%s, TLat=%s, LLong=%s, RLong=%s, zoom=%s", //$NON-NLS-1$ cBottomLatitude, cTopLatitude, cLeftLongitude, cRightLongitude, zoom)); long now = System.currentTimeMillis(); if (files.isEmpty()) { cObjectsBox = dataBox; cObjects = new ArrayList<BinaryMapDataObject>(); return true; } try { int count = 0; ArrayList<BinaryMapDataObject> tempList = new ArrayList<BinaryMapDataObject>(); System.gc(); // to clear previous objects TLongSet ids = new TLongHashSet(); Map<TagValuePair, List<BinaryMapDataObject>> multiPolygons = new LinkedHashMap<TagValuePair, List<BinaryMapDataObject>>(); int leftX = MapUtils.get31TileNumberX(cLeftLongitude); int rightX = MapUtils.get31TileNumberX(cRightLongitude); int bottomY = MapUtils.get31TileNumberY(cBottomLatitude); int topY = MapUtils.get31TileNumberY(cTopLatitude); searchRequest = BinaryMapIndexReader.buildSearchRequest(leftX, rightX, topY, bottomY, zoom); if (zoom < 17) { searchRequest.setSearchFilter(new BinaryMapIndexReader.SearchFilter() { @Override public boolean accept(TIntArrayList types, BinaryMapIndexReader.MapIndex root) { for (int j = 0; j < types.size(); j++) { int type = types.get(j); int mask = type & 3; TagValuePair pair = root.decodeType(type); if (pair != null && renderingType.isObjectVisible(pair.tag, pair.value, zoom, mask)) { return true; } if(pair != null && mask == OsmandRenderingRulesParser.POINT_STATE && renderingType.isObjectVisible(pair.tag, pair.value, zoom, OsmandRenderingRulesParser.TEXT_STATE)){ return true; } } return false; } }); } for (BinaryMapIndexReader c : files.values()) { List<BinaryMapDataObject> res = c.searchMapIndex(searchRequest); if (checkWhetherInterrupted()) { return false; } for (BinaryMapDataObject r : res) { if (PerformanceFlags.checkForDuplicateObjectIds) { if (ids.contains(r.getId())) { // do not add object twice continue; } ids.add(r.getId()); } count++; for(int i=0; i < r.getTypes().length; i++){ if ((r.getTypes()[i] & 0x3) == MapRenderingTypes.MULTY_POLYGON_TYPE) { // multy polygon r.getId() >> 3 TagValuePair pair = r.getMapIndex().decodeType(MapRenderingTypes.getMainObjectType(r.getTypes()[i]), MapRenderingTypes.getObjectSubType(r.getTypes()[i])); if(pair != null){ pair = new TagValuePair(pair.tag, pair.value, r.getTypes()[i]); if (!multiPolygons.containsKey(pair)) { multiPolygons.put(pair, new ArrayList<BinaryMapDataObject>()); } multiPolygons.get(pair).add(r); } } } if (checkWhetherInterrupted()) { return false; } tempList.add(r); } searchRequest.clearSearchResults(); } List<MultyPolygon> pMulti = proccessMultiPolygons(multiPolygons, leftX, rightX, bottomY, topY, zoom); tempList.addAll(pMulti); log.info(String.format("Search has been done in %s ms. %s results were found.", System.currentTimeMillis() - now, count)); //$NON-NLS-1$ cObjects = tempList; cObjectsBox = dataBox; } catch (IOException e) { log.debug("Search failed", e); //$NON-NLS-1$ return false; } return true; } public synchronized void loadMap(RotatedTileBox tileRect, List<IMapDownloaderCallback> notifyList) { interrupted = false; if(currentRenderingContext != null){ currentRenderingContext = null; } try { // find selected rendering type Boolean renderDay = ((OsmandApplication)context.getApplicationContext()).getDaynightHelper().getDayNightRenderer(); BaseOsmandRender renderingType = RendererRegistry.getRegistry().getCurrentSelectedRenderer(); if(renderDay != null && renderingType != null && renderDay.booleanValue() != renderingType.isDayRender()){ renderingType = RendererRegistry.getRegistry().getOppositeRendererForDayNight(renderingType); } // prevent editing requestedBox = new RotatedTileBox(tileRect); // calculate data box RectF dataBox = requestedBox.calculateLatLonBox(new RectF()); long now = System.currentTimeMillis(); if (cObjectsBox.left > dataBox.left || cObjectsBox.top > dataBox.top || cObjectsBox.right < dataBox.right || cObjectsBox.bottom < dataBox.bottom) { // increase data box in order for rotate if ((dataBox.right - dataBox.left) > (dataBox.top - dataBox.bottom)) { double wi = (dataBox.right - dataBox.left) * .2; dataBox.left -= wi; dataBox.right += wi; } else { double hi = (dataBox.bottom - dataBox.top) * .2; dataBox.top -= hi; dataBox.bottom += hi; } boolean loaded = loadVectorData(dataBox, requestedBox.getZoom(), renderingType); if (!loaded || checkWhetherInterrupted()) { return; } } final long searchTime = System.currentTimeMillis() - now; currentRenderingContext = new OsmandRenderer.RenderingContext(); currentRenderingContext.leftX = (float) requestedBox.getLeftTileX(); currentRenderingContext.topY = (float) requestedBox.getTopTileY(); currentRenderingContext.zoom = requestedBox.getZoom(); currentRenderingContext.rotate = requestedBox.getRotate(); currentRenderingContext.width = (int) (requestedBox.getTileWidth() * OsmandRenderer.TILE_SIZE); currentRenderingContext.height = (int) (requestedBox.getTileHeight() * OsmandRenderer.TILE_SIZE); if (checkWhetherInterrupted()) { return; } now = System.currentTimeMillis(); Bitmap bmp = Bitmap.createBitmap(currentRenderingContext.width, currentRenderingContext.height, Config.RGB_565); boolean stepByStep = OsmandSettings.isUsingStepByStepRendering(prefs); // 1. generate image step by step if (stepByStep) { this.bmp = bmp; this.bmpLocation = tileRect; } renderer.generateNewBitmap(currentRenderingContext, cObjects, bmp, OsmandSettings.usingEnglishNames(prefs), renderingType, stepByStep ? notifyList : null); if (checkWhetherInterrupted()) { currentRenderingContext = null; return; } final long renderingTime = System.currentTimeMillis() - now; currentRenderingContext = null; // 2. replace whole image if (!stepByStep) { this.bmp = bmp; this.bmpLocation = tileRect; } if(OsmandSettings.isDebugRendering(context)){ final String msg = "Search done in "+ searchTime+" ms\nRendering done in "+ renderingTime+ " ms"; //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$ handler.post(new Runnable(){ @Override public void run() { Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); } }); } } catch (RuntimeException e) { log.error("Runtime memory exception", e); //$NON-NLS-1$ handler.post(new Runnable(){ @Override public void run() { Toast.makeText(context, R.string.rendering_exception, Toast.LENGTH_SHORT).show(); } }); } catch (OutOfMemoryError e) { log.error("Out of memory error", e); //$NON-NLS-1$ cObjects = new ArrayList<BinaryMapDataObject>(); cObjectsBox = new RectF(); handler.post(new Runnable(){ @Override public void run() { Toast.makeText(context, R.string.rendering_out_of_memory, Toast.LENGTH_SHORT).show(); } }); } } public Bitmap getBitmap() { return bmp; } public synchronized void clearCache() { cObjects = new ArrayList<BinaryMapDataObject>(); cObjectsBox = new RectF(); if(bmp != null){ bmp = null; } requestedBox = bmpLocation = null; } /// Manipulating with multipolygons public List<MultyPolygon> proccessMultiPolygons(Map<TagValuePair, List<BinaryMapDataObject>> multyPolygons, int leftX, int rightX, int bottomY, int topY, int zoom){ List<MultyPolygon> listPolygons = new ArrayList<MultyPolygon>(multyPolygons.size()); List<List<Long>> completedRings = new ArrayList<List<Long>>(); List<List<Long>> incompletedRings = new ArrayList<List<Long>>(); List<String> completedRingNames = new ArrayList<String>(); List<String> incompletedRingNames = new ArrayList<String>(); for (TagValuePair type : multyPolygons.keySet()) { List<BinaryMapDataObject> directList; List<BinaryMapDataObject> inverselist; if(((type.additionalAttribute >> 15) & 1) == 1){ TagValuePair directType = new TagValuePair(type.tag, type.value, type.additionalAttribute & ((1 << 15) - 1)); if (!multyPolygons.containsKey(directType)) { inverselist = multyPolygons.get(type); directList = Collections.emptyList(); } else { // continue on inner boundaries continue; } } else { TagValuePair inverseType = new TagValuePair(type.tag, type.value, type.additionalAttribute | (1 << 15)); directList = multyPolygons.get(type); inverselist = Collections.emptyList(); if (multyPolygons.containsKey(inverseType)) { inverselist = multyPolygons.get(inverseType); } } completedRings.clear(); incompletedRings.clear(); completedRingNames.clear(); incompletedRingNames.clear(); log.debug("Process multypolygon " + type.tag + " " + type.value + //$NON-NLS-1$ //$NON-NLS-2$ " direct list : " +directList + " rev : " + inverselist); //$NON-NLS-1$ //$NON-NLS-2$ MultyPolygon pl = processMultiPolygon(leftX, rightX, bottomY, topY, listPolygons, completedRings, incompletedRings, completedRingNames, incompletedRingNames, type, directList, inverselist, zoom); if(pl != null){ listPolygons.add(pl); } } return listPolygons; } private MultyPolygon processMultiPolygon(int leftX, int rightX, int bottomY, int topY, List<MultyPolygon> listPolygons, List<List<Long>> completedRings, List<List<Long>> incompletedRings, List<String> completedRingNames, List<String> incompletedRingNames, TagValuePair type, List<BinaryMapDataObject> directList, List<BinaryMapDataObject> inverselist, int zoom) { MultyPolygon pl = new MultyPolygon(); // delete direction last bit (to not show point) pl.setTag(type.tag); pl.setValue(type.value); pl.setLayer(MapRenderingTypes.getNegativeWayLayer(type.additionalAttribute)); long dbId = 0; for (int km = 0; km < 2; km++) { List<BinaryMapDataObject> list = km == 0 ? directList : inverselist; for (BinaryMapDataObject o : list) { int len = o.getPointsLength(); if (len < 2) { continue; } dbId = o.getId() >> 3; List<Long> coordinates = new ArrayList<Long>(o.getPointsLength() / 2); int px = o.getPoint31XTile(km == 0 ? 0 : len - 1); int py = o.getPoint31YTile(km == 0 ? 0 : len - 1); int x = px; int y = py; boolean pinside = leftX <= x && x <= rightX && y >= topY && y <= bottomY; if (pinside) { coordinates.add((((long) x) << 32) | ((long) y)); } for (int i = 1; i < len; i++) { x = o.getPoint31XTile(km == 0 ? i : len - i - 1); y = o.getPoint31YTile(km == 0 ? i : len - i - 1); boolean inside = leftX <= x && x <= rightX && y >= topY && y <= bottomY; boolean lineEnded = calculateLineCoordinates(inside, x, y, pinside, px, py, leftX, rightX, bottomY, topY, coordinates); if(lineEnded){ processMultipolygonLine(completedRings, incompletedRings, completedRingNames, incompletedRingNames, coordinates, o.getName()); // create new line if it goes outside coordinates = new ArrayList<Long>(); } px = x; py = y; pinside = inside; } processMultipolygonLine(completedRings, incompletedRings, completedRingNames, incompletedRingNames, coordinates, o.getName()); } } if(completedRings.size() == 0 && incompletedRings.size() == 0){ return null; } if (incompletedRings.size() > 0) { unifyIncompletedRings(incompletedRings, completedRings, completedRingNames, incompletedRingNames, leftX, rightX, bottomY, topY, dbId, zoom); } else { // due to self intersection small objects (for low zooms check only coastline) if (zoom >= 13 || ("natural".equals(type.tag) && "coastline".equals(type.value))) { //$NON-NLS-1$//$NON-NLS-2$ boolean clockwiseFound = false; for (List<Long> c : completedRings) { if (isClockwiseWay(c)) { clockwiseFound = true; break; } } if (!clockwiseFound) { // add whole bound List<Long> whole = new ArrayList<Long>(4); whole.add((((long) leftX) << 32) | ((long) topY)); whole.add((((long) rightX) << 32) | ((long) topY)); whole.add((((long) rightX) << 32) | ((long) bottomY)); whole.add((((long) leftX) << 32) | ((long) bottomY)); completedRings.add(whole); log.info("!!! Isolated island !!!"); //$NON-NLS-1$ } } } long[][] lns = new long[completedRings.size()][]; for (int i = 0; i < completedRings.size(); i++) { List<Long> ring = completedRings.get(i); lns[i] = new long[ring.size()]; for (int j = 0; j < lns[i].length; j++) { lns[i][j] = ring.get(j); } } pl.setNames(completedRingNames.toArray(new String[completedRings.size()])); pl.setLines(lns); return pl; } private boolean isClockwiseWay(List<Long> c){ double angle = 0; double prevAng = 0; int px = 0; int py = 0; int mask = 0xffffffff; for (int i = 0; i < c.size(); i++) { int x = (int) (c.get(i) >> 32); int y = (int) (c.get(i) & mask); if (i >= 1) { double ang = Math.atan2(py - y, x - px); if (i > 1) { double delta = (ang - prevAng); if (delta < -Math.PI) { delta += 2 * Math.PI; } else if (delta > Math.PI) { delta -= 2 * Math.PI; } angle += delta; prevAng = ang; } else { prevAng = ang; } } px = x; py = y; } return angle < 0; } private void processMultipolygonLine(List<List<Long>> completedRings, List<List<Long>> incompletedRings, List<String> completedRingsNames, List<String> incompletedRingsNames, List<Long> coordinates, String name) { if (coordinates.size() > 0) { if (coordinates.get(0).longValue() == coordinates.get(coordinates.size() - 1).longValue()) { completedRings.add(coordinates); completedRingsNames.add(name); } else { boolean add = true; for (int k = 0; k < incompletedRings.size();) { boolean remove = false; List<Long> i = incompletedRings.get(k); String oldName = incompletedRingsNames.get(k); if (coordinates.get(0).longValue() == i.get(i.size() - 1).longValue()) { i.addAll(coordinates.subList(1, coordinates.size())); remove = true; coordinates = i; } else if (coordinates.get(coordinates.size() - 1).longValue() == i.get(0).longValue()) { coordinates.addAll(i.subList(1, i.size())); remove = true; } if (remove) { incompletedRings.remove(k); incompletedRingsNames.remove(k); } else { k++; } if (coordinates.get(0).longValue() == coordinates.get(coordinates.size() - 1).longValue()) { completedRings.add(coordinates); if(oldName != null){ completedRingsNames.add(oldName); } else { completedRingsNames.add(name); } add = false; break; } } if (add) { incompletedRings.add(coordinates); incompletedRingsNames.add(name); } } } } private void unifyIncompletedRings(List<List<Long>> incompletedRings, List<List<Long>> completedRings, List<String> completedRingNames, List<String> incompletedRingNames, int leftX, int rightX, int bottomY, int topY, long dbId, int zoom) { int mask = 0xffffffff; Set<Integer> nonvisitedRings = new LinkedHashSet<Integer>(); for(int j = 0; j< incompletedRings.size(); j++){ List<Long> i = incompletedRings.get(j); int x = (int) (i.get(i.size() - 1) >> 32); int y = (int) (i.get(i.size() - 1) & mask); int sx = (int) (i.get(0) >> 32); int sy = (int) (i.get(0) & mask); boolean st = y == topY || x == rightX || y == bottomY || x == leftX; boolean end = sy == topY || sx == rightX || sy == bottomY || sx == leftX; // something wrong here // These exceptions are used to check logic about processing multipolygons // However in map data this situation could happen with broken multipolygons (so it would data causes app error) // that's why these exceptions could be replaced with return; statement. if (!end || !st) { float dx = (float) MapUtils.get31LongitudeX(x); float dsx = (float) MapUtils.get31LongitudeX(sx); float dy = (float) MapUtils.get31LatitudeY(y); float dsy = (float) MapUtils.get31LatitudeY(sy); String str; if(!end){ str = " Start point (to close) not found : end_x = {0}, end_y = {1}, start_x = {2}, start_y = {3} : bounds {4} {5} - {6} {7}"; //$NON-NLS-1$ System.err.println( MessageFormat.format(dbId + str, dx, dy, dsx, dsy, leftX+"", topY+"", rightX+"", bottomY+"")); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$ } if(!st){ str = " End not found : end_x = {0}, end_y = {1}, start_x = {2}, start_y = {3} : bounds {4} {5} - {6} {7}"; //$NON-NLS-1$ System.err.println( MessageFormat.format(dbId + str, dx, dy, dsx, dsy, leftX+"", topY+"", rightX+"", bottomY+"")); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$ } } else { nonvisitedRings.add(j); } } for(int j = 0; j< incompletedRings.size(); j++){ List<Long> i = incompletedRings.get(j); String name = incompletedRingNames.get(j); if(!nonvisitedRings.contains(j)){ continue; } int x = (int) (i.get(i.size() - 1) >> 32); int y = (int) (i.get(i.size() - 1) & mask); // 31 - (zoom + 8) int EVAL_DELTA = 6 << (23 - zoom); int UNDEFINED_MIN_DIFF = -1 - EVAL_DELTA; while (true) { int st = 0; // st already checked to be one of the four if (y == topY) { st = 0; } else if (x == rightX) { st = 1; } else if (y == bottomY) { st = 2; } else if (x == leftX) { st = 3; } int nextRingIndex = -1; // BEGIN go clockwise around rectangle for (int h = st; h < st + 4; h++) { // BEGIN find closest nonvisited start (including current) int mindiff = UNDEFINED_MIN_DIFF; for (Integer ni : nonvisitedRings) { List<Long> cni = incompletedRings.get(ni); int csx = (int) (cni.get(0) >> 32); int csy = (int) (cni.get(0) & mask); if (h % 4 == 0) { // top if (csy == topY && csx >= x - EVAL_DELTA) { if (mindiff == UNDEFINED_MIN_DIFF || (csx - x) <= mindiff) { mindiff = (csx - x); nextRingIndex = ni; } } } else if (h % 4 == 1) { // right if (csx == rightX && csy >= y - EVAL_DELTA) { if (mindiff == UNDEFINED_MIN_DIFF || (csy - y) <= mindiff) { mindiff = (csy - y); nextRingIndex = ni; } } } else if (h % 4 == 2) { // bottom if (csy == bottomY && csx <= x + EVAL_DELTA) { if (mindiff == UNDEFINED_MIN_DIFF || (x - csx) <= mindiff) { mindiff = (x - csx); nextRingIndex = ni; } } } else if (h % 4 == 3) { // left if (csx == leftX && csy <= y + EVAL_DELTA) { if (mindiff == UNDEFINED_MIN_DIFF || (y - csy) <= mindiff) { mindiff = (y - csy); nextRingIndex = ni; } } } } // END find closest start (including current) // we found start point if (mindiff != UNDEFINED_MIN_DIFF) { break; } else { if (h % 4 == 0) { // top y = topY; x = rightX; } else if (h % 4 == 1) { // right y = bottomY; x = rightX; } else if (h % 4 == 2) { // bottom y = bottomY; x = leftX; } else if (h % 4 == 3) { y = topY; x = leftX; } i.add((((long) x) << 32) | ((long) y)); } } // END go clockwise around rectangle if (nextRingIndex == -1) { // it is impossible (current start should always be found) } else if (nextRingIndex == j) { i.add(i.get(0)); nonvisitedRings.remove(j); break; } else { i.addAll(incompletedRings.get(nextRingIndex)); nonvisitedRings.remove(nextRingIndex); // get last point and start again going clockwise x = (int) (i.get(i.size() - 1) >> 32); y = (int) (i.get(i.size() - 1) & mask); } } completedRings.add(i); completedRingNames.add(name); } } /** * @return -1 if there is no instersection or x<<32 | y */ private long calculateIntersection(int x, int y, int px, int py, int leftX, int rightX, int bottomY, int topY){ int by = -1; int bx = -1; // firstly try to search if the line goes in if (py < topY && y >= topY) { int tx = (int) (px + ((double) (x - px) * (topY - py)) / (y - py)); if (leftX <= tx && tx <= rightX) { bx = tx; by = topY; return (((long) bx) << 32) | ((long) by); } } if (py > bottomY && y <= bottomY) { int tx = (int) (px + ((double) (x - px) * (py - bottomY)) / (py - y)); if (leftX <= tx && tx <= rightX) { bx = tx; by = bottomY; return (((long) bx) << 32) | ((long) by); } } if (px < leftX && x >= leftX) { int ty = (int) (py + ((double) (y - py) * (leftX - px)) / (x - px)); if (ty >= topY && ty <= bottomY) { by = ty; bx = leftX; return (((long) bx) << 32) | ((long) by); } } if (px > rightX && x <= rightX) { int ty = (int) (py + ((double) (y - py) * (px - rightX)) / (px - x)); if (ty >= topY && ty <= bottomY) { by = ty; bx = rightX; return (((long) bx) << 32) | ((long) by); } } // try to search if point goes out if (py > topY && y <= topY) { int tx = (int) (px + ((double) (x - px) * (topY - py)) / (y - py)); if (leftX <= tx && tx <= rightX) { bx = tx; by = topY; return (((long) bx) << 32) | ((long) by); } } if (py < bottomY && y >= bottomY) { int tx = (int) (px + ((double) (x - px) * (py - bottomY)) / (py - y)); if (leftX <= tx && tx <= rightX) { bx = tx; by = bottomY; return (((long) bx) << 32) | ((long) by); } } if (px > leftX && x <= leftX) { int ty = (int) (py + ((double) (y - py) * (leftX - px)) / (x - px)); if (ty >= topY && ty <= bottomY) { by = ty; bx = leftX; return (((long) bx) << 32) | ((long) by); } } if (px < rightX && x >= rightX) { int ty = (int) (py + ((double) (y - py) * (px - rightX)) / (px - x)); if (ty >= topY && ty <= bottomY) { by = ty; bx = rightX; return (((long) bx) << 32) | ((long) by); } } if(px == rightX || px == leftX || py == topY || py == bottomY){ bx = px; by = py; } return -1l; } private boolean calculateLineCoordinates(boolean inside, int x, int y, boolean pinside, int px, int py, int leftX, int rightX, int bottomY, int topY, List<Long> coordinates) { boolean lineEnded = false; if (pinside) { if (!inside) { long is = calculateIntersection(x, y, px, py, leftX, rightX, bottomY, topY); if (is == -1) { // it is an error (!) is = (((long) px) << 32) | ((long) py); } coordinates.add(is); lineEnded = true; } else { coordinates.add((((long) x) << 32) | ((long) y)); } } else { long is = calculateIntersection(x, y, px, py, leftX, rightX, bottomY, topY); if(inside){ // assert is != -1; coordinates.add(is); coordinates.add((((long) x) << 32) | ((long) y)); } else if(is != -1){ int bx = (int) (is >> 32); int by = (int) (is & 0xffffffff); coordinates.add(is); is = calculateIntersection(x, y, bx, by, leftX, rightX, bottomY, topY); coordinates.add(is); lineEnded = true; } } return lineEnded; } }