diff --git a/AUTHORS b/AUTHORS deleted file mode 100644 index 16c27abd73..0000000000 --- a/AUTHORS +++ /dev/null @@ -1,17 +0,0 @@ -Copyright © OsmAnd 2010–2014 -### Credits to all major contributors/developers: - * Victor Shcherb – all parts of the project, originator - * Pavol Zibrita – first contributor and developer of some utilities - * Dusan Kazik – one of the first contributors - * Andre Van Atten – project supporter, active forum participant, one of the first users. - * Dr. Hardy Mueller – map appearance concept and base renderers, international consistency and testing, usability, app scoping, concepts, documentation, wiki, market research. - * Yvecai – main contributor to Contour Lines and Hillshade maps - * Alexey Pelykh – C++ developer, created native library and made application much snappier. - * Max (Zahnstocher) – Java contributor, active forum participant. - * Harry van der Wolf – contributor to country boundaries, configuration files, address files, and much else; active forum participant. - * Robin ‘ypid’ Schneider – opening hours contributor - -### Other Pull requests -Copyright © All authors of translations and pull requests could be found in commits history: - - Translations are under special “contributor” name ‘weblate’ - - Pull requests have two committers, first is original contributor and second is project maintainer diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000000..c418f81d10 --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,7 @@ +### Credits to all major contributors/developers: +Major contributors /developers listed here https://github.com/osmandapp/osmandapp.github.io/blob/master/website/help/about.html#L8 + +### Other Pull requests +Copyright © All authors of translations and pull requests could be found in commits history: + - Translations are under special “contributor” name ‘weblate’ + - Pull requests have two committers, first is original contributor and second is project maintainer diff --git a/OsmAnd-java/build.gradle b/OsmAnd-java/build.gradle index 093c9747e9..827b63393b 100644 --- a/OsmAnd-java/build.gradle +++ b/OsmAnd-java/build.gradle @@ -40,9 +40,15 @@ task collectRegionsInfoResources(type: Copy) { } task collectTestResources(type: Copy) { - from "../../resources/test-resources" into "src/test/resources/" - include "*" + from("../../resources/test-resources") { + include "*" + } + from("../../resources/poi") { + include "poi_types.xml" + include "/phrases/en/*" + //include "/phrases/ru/*" + } } diff --git a/OsmAnd-java/src/main/java/net/osmand/CollatorStringMatcher.java b/OsmAnd-java/src/main/java/net/osmand/CollatorStringMatcher.java index d6011c6209..693be4bd82 100644 --- a/OsmAnd-java/src/main/java/net/osmand/CollatorStringMatcher.java +++ b/OsmAnd-java/src/main/java/net/osmand/CollatorStringMatcher.java @@ -124,6 +124,9 @@ public class CollatorStringMatcher implements StringMatcher { public static boolean cstartsWith(Collator collator, String searchInParam, String theStart, boolean checkBeginning, boolean checkSpaces, boolean equals, boolean trim) { String searchIn = searchInParam.toLowerCase(Locale.getDefault()); + if (trim && searchIn.length() > 0) { + searchIn += " "; + } int searchInLength = searchIn.length(); if (trim && searchInLength > 0 && theStart.length() > searchInLength) { theStart = theStart.substring(0, searchInLength); diff --git a/OsmAnd-java/src/main/java/net/osmand/NativeLibrary.java b/OsmAnd-java/src/main/java/net/osmand/NativeLibrary.java index 3eaebd56e5..ed2ed17c17 100644 --- a/OsmAnd-java/src/main/java/net/osmand/NativeLibrary.java +++ b/OsmAnd-java/src/main/java/net/osmand/NativeLibrary.java @@ -130,10 +130,10 @@ public class NativeLibrary { public RouteSegmentResult[] runNativeRouting(int sx31, int sy31, int ex31, int ey31, RoutingConfiguration config, RouteRegion[] regions, RouteCalculationProgress progress, PrecalculatedRouteDirection precalculatedRouteDirection, - boolean basemap) { + boolean basemap, boolean publicTransport, boolean startTransportStop, boolean targetTransportStop) { // config.router.printRules(System.out); return nativeRouting(new int[] { sx31, sy31, ex31, ey31 }, config, config.initialDirection == null ? -360 : config.initialDirection.floatValue(), - regions, progress, precalculatedRouteDirection, basemap); + regions, progress, precalculatedRouteDirection, basemap, publicTransport, startTransportStop, targetTransportStop); } @@ -156,7 +156,9 @@ public class NativeLibrary { protected static native RouteDataObject[] getRouteDataObjects(RouteRegion reg, long rs, int x31, int y31); protected static native RouteSegmentResult[] nativeRouting(int[] coordinates, RoutingConfiguration r, - float initDirection, RouteRegion[] regions, RouteCalculationProgress progress, PrecalculatedRouteDirection precalculatedRouteDirection, boolean basemap); + float initDirection, RouteRegion[] regions, RouteCalculationProgress progress, + PrecalculatedRouteDirection precalculatedRouteDirection, boolean basemap, + boolean publicTransport, boolean startTransportStop, boolean targetTransportStop); protected static native void deleteSearchResult(long searchResultHandle); diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java index 964f714865..65493a75ed 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapIndexReader.java @@ -1,37 +1,9 @@ package net.osmand.binary; -import gnu.trove.list.array.TIntArrayList; -import gnu.trove.map.TIntObjectMap; -import gnu.trove.map.hash.TIntObjectHashMap; -import gnu.trove.map.hash.TLongObjectHashMap; -import gnu.trove.set.hash.TIntHashSet; - -import java.io.BufferedInputStream; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.RandomAccessFile; -import java.io.Reader; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.WireFormat; import net.osmand.Collator; import net.osmand.CollatorStringMatcher; @@ -72,9 +44,37 @@ import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; -import com.google.protobuf.CodedInputStream; -import com.google.protobuf.CodedOutputStream; -import com.google.protobuf.WireFormat; +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.RandomAccessFile; +import java.io.Reader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import gnu.trove.list.array.TIntArrayList; +import gnu.trove.map.TIntObjectMap; +import gnu.trove.map.hash.TIntObjectHashMap; +import gnu.trove.map.hash.TLongObjectHashMap; +import gnu.trove.set.hash.TIntHashSet; public class BinaryMapIndexReader { @@ -130,7 +130,7 @@ public class BinaryMapIndexReader { init(); } - /*private */BinaryMapIndexReader(final RandomAccessFile raf, File file, boolean init) throws IOException { + public BinaryMapIndexReader(final RandomAccessFile raf, File file, boolean init) throws IOException { this.raf = raf; this.file = file; codedIS = CodedInputStream.newInstance(raf); @@ -554,13 +554,15 @@ public class BinaryMapIndexReader { } return false; } - public List searchTransportIndex(SearchRequest req) throws IOException { for (TransportIndex index : transportIndexes) { if (index.stopsFileLength == 0 || index.right < req.left || index.left > req.right || index.top > req.bottom || index.bottom < req.top) { continue; } + if (req.stringTable != null) { + req.stringTable.clear(); + } codedIS.seek(index.stopsFileOffset); int oldLimit = codedIS.pushLimit(index.stopsFileLength); int offset = req.searchResults.size(); @@ -875,6 +877,8 @@ public class BinaryMapIndexReader { continue; } + + // lazy initializing trees if (index.trees == null) { index.trees = new ArrayList(); @@ -2074,14 +2078,14 @@ public class BinaryMapIndexReader { private static boolean testAddressSearch = false; private static boolean testAddressSearchName = false; private static boolean testAddressJustifySearch = false; - private static boolean testPoiSearch = true; + private static boolean testPoiSearch = false; private static boolean testPoiSearchOnPath = false; private static boolean testTransportSearch = true; - private static int sleft = MapUtils.get31TileNumberX(4.7495); - private static int sright = MapUtils.get31TileNumberX(4.8608); - private static int stop = MapUtils.get31TileNumberY(52.3395); - private static int sbottom = MapUtils.get31TileNumberY(52.2589); + private static int sleft = MapUtils.get31TileNumberX(27.55079); + private static int sright = MapUtils.get31TileNumberX(27.55317); + private static int stop = MapUtils.get31TileNumberY(53.89378); + private static int sbottom = MapUtils.get31TileNumberY(53.89276); private static int szoom = 15; private static void println(String s) { @@ -2090,7 +2094,7 @@ public class BinaryMapIndexReader { public static void main(String[] args) throws IOException { File fl = new File(System.getProperty("maps") + "/Synthetic_test_rendering.obf"); - fl = new File(System.getProperty("maps") + "/Map.obf"); + fl = new File(System.getProperty("maps") + "/Belarus_europe_2.obf"); RandomAccessFile raf = new RandomAccessFile(fl, "r"); @@ -2280,6 +2284,9 @@ public class BinaryMapIndexReader { println(" " + route.getRef() + " " + route.getName() + " " + route.getDistance() + " " + route.getAvgBothDistance()); StringBuilder b = new StringBuilder(); + if(route.getForwardWays() == null) { + continue; + } for(Way w : route.getForwardWays()) { b.append(w.getNodes()).append(" "); } diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapTransportReaderAdapter.java b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapTransportReaderAdapter.java index 3cf386843c..fce39c5c55 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapTransportReaderAdapter.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/BinaryMapTransportReaderAdapter.java @@ -5,17 +5,20 @@ import gnu.trove.map.hash.TIntObjectHashMap; import java.io.IOException; import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import net.osmand.binary.BinaryMapIndexReader.SearchRequest; -import net.osmand.binary.OsmandOdb.TransportRouteSchedule; import net.osmand.data.TransportSchedule; import net.osmand.data.TransportStop; +import net.osmand.data.TransportStopExit; import net.osmand.osm.edit.Node; import net.osmand.osm.edit.Way; import net.osmand.util.MapUtils; import net.sf.junidecode.Junidecode; -import com.google.protobuf.ByteString; import com.google.protobuf.CodedInputStream; import com.google.protobuf.WireFormat; @@ -238,6 +241,11 @@ public class BinaryMapTransportReaderAdapter { stringTable.putIfAbsent(i, ""); return ((char) i)+""; } + + private String regStr(TIntObjectHashMap stringTable, int i) throws IOException{ + stringTable.putIfAbsent(i, ""); + return ((char) i)+""; + } public net.osmand.data.TransportRoute getTransportRoute(int filePointer, TIntObjectHashMap stringTable, boolean onlyDescription) throws IOException { @@ -394,12 +402,12 @@ public class BinaryMapTransportReaderAdapter { int oldLimit = codedIS.pushLimit(ind.stringTable.length); int current = 0; int i = 0; - while (i < values.length) { + while (i < values.length && codedIS.getBytesUntilLimit() > 0) { int t = codedIS.readTag(); int tag = WireFormat.getTagFieldNumber(t); switch (tag) { case 0: - break; + return; case OsmandOdb.StringTable.S_FIELD_NUMBER: if (current == values[i]) { String value = codedIS.readString(); @@ -446,12 +454,26 @@ public class BinaryMapTransportReaderAdapter { } protected void initializeNames(TIntObjectHashMap stringTable, TransportStop s) { + for (TransportStopExit exit : s.getExits()) { + if (exit.getRef().length() > 0) { + exit.setRef(stringTable.get(exit.getRef().charAt(0))); + } + } if (s.getName().length() > 0) { s.setName(stringTable.get(s.getName().charAt(0))); } if (s.getEnName(false).length() > 0) { s.setEnName(stringTable.get(s.getEnName(false).charAt(0))); } + Map namesMap = new HashMap<>(s.getNamesMap(false)); + if (!s.getNamesMap(false).isEmpty()) { + s.getNamesMap(false).clear(); + } + Iterator> it = namesMap.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry e = it.next(); + s.setName(stringTable.get(e.getKey().charAt(0)), stringTable.get(e.getValue().charAt(0))); + } } @@ -517,6 +539,7 @@ public class BinaryMapTransportReaderAdapter { TransportStop dataObject = new TransportStop(); dataObject.setLocation(BinaryMapIndexReader.TRANSPORT_STOP_ZOOM, x, y); dataObject.setFileOffset(shift); + List names = null; while(true){ int t = codedIS.readTag(); tag = WireFormat.getTagFieldNumber(t); @@ -543,16 +566,76 @@ public class BinaryMapTransportReaderAdapter { } else { skipUnknownField(t); } - + break; + case OsmandOdb.TransportStop.ADDITIONALNAMEPAIRS_FIELD_NUMBER : + if (req.stringTable != null) { + int sizeL = codedIS.readRawVarint32(); + int oldRef = codedIS.pushLimit(sizeL); + while (codedIS.getBytesUntilLimit() > 0) { + dataObject.setName(regStr(req.stringTable,codedIS.readRawVarint32()), + regStr(req.stringTable,codedIS.readRawVarint32())); + } + codedIS.popLimit(oldRef); + } else { + skipUnknownField(t); + } break; case OsmandOdb.TransportStop.ID_FIELD_NUMBER : dataObject.setId(codedIS.readSInt64()); break; + case OsmandOdb.TransportStop.EXITS_FIELD_NUMBER : + int length = codedIS.readRawVarint32(); + int oldLimit = codedIS.pushLimit(length); + + TransportStopExit transportStopExit = readTransportStopExit(cleft, ctop, req); + dataObject.addExit(transportStopExit); + codedIS.popLimit(oldLimit); + break; default: skipUnknownField(t); break; } } } - + + private TransportStopExit readTransportStopExit(int cleft, int ctop, SearchRequest req) throws IOException { + + TransportStopExit dataObject = new TransportStopExit(); + int x = 0; + int y = 0; + + while (true) { + int t = codedIS.readTag(); + int tag = WireFormat.getTagFieldNumber(t); + + switch (tag) { + case 0: + if (dataObject.getName("en").length() == 0) { + dataObject.setEnName(Junidecode.unidecode(dataObject.getName())); + } + if (x != 0 || y != 0) { + dataObject.setLocation(BinaryMapIndexReader.TRANSPORT_STOP_ZOOM, x, y); + } + return dataObject; + case OsmandOdb.TransportStopExit.REF_FIELD_NUMBER: + if (req.stringTable != null) { + dataObject.setRef(regStr(req.stringTable)); + } else { + skipUnknownField(t); + } + break; + case OsmandOdb.TransportStopExit.DX_FIELD_NUMBER: + x = codedIS.readSInt32() + cleft; + break; + case OsmandOdb.TransportStopExit.DY_FIELD_NUMBER: + y = codedIS.readSInt32() + ctop; + break; + default: + skipUnknownField(t); + break; + } + } + } + + } diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java b/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java index d73c3f083a..55d7461573 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/CommonWords.java @@ -28,7 +28,7 @@ public class CommonWords { public static int getCommonSearch(String name) { Integer i = commonWordsDictionary.get(name); - return i == null ? getFrequentlyUsed(name) : i.intValue(); + return i == null ? getFrequentlyUsed(name) : i.intValue() + frequentlyUsedWordsDictionary.size(); } public static int getCommonGeocoding(String name) { diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/OsmandOdb.java b/OsmAnd-java/src/main/java/net/osmand/binary/OsmandOdb.java index 4a29912436..36237c73d4 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/OsmandOdb.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/OsmandOdb.java @@ -35628,6 +35628,49 @@ public final class OsmandOdb { */ int getNameEn(); + // optional bytes additionalNamePairs = 8; + /** + * optional bytes additionalNamePairs = 8; + * + *
+     * array of bytes (array of pairs <raw var int>)
+     * 
+ */ + boolean hasAdditionalNamePairs(); + /** + * optional bytes additionalNamePairs = 8; + * + *
+     * array of bytes (array of pairs <raw var int>)
+     * 
+ */ + com.google.protobuf.ByteString getAdditionalNamePairs(); + + // repeated .OsmAnd.OBF.TransportStopExit exits = 9; + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + java.util.List + getExitsList(); + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + net.osmand.binary.OsmandOdb.TransportStopExit getExits(int index); + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + int getExitsCount(); + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + java.util.List + getExitsOrBuilderList(); + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + net.osmand.binary.OsmandOdb.TransportStopExitOrBuilder getExitsOrBuilder( + int index); + // repeated uint32 routes = 16; /** * repeated uint32 routes = 16; @@ -35730,10 +35773,23 @@ public final class OsmandOdb { nameEn_ = input.readUInt32(); break; } + case 66: { + bitField0_ |= 0x00000020; + additionalNamePairs_ = input.readBytes(); + break; + } + case 74: { + if (!((mutable_bitField0_ & 0x00000040) == 0x00000040)) { + exits_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000040; + } + exits_.add(input.readMessage(net.osmand.binary.OsmandOdb.TransportStopExit.PARSER, extensionRegistry)); + break; + } case 128: { - if (!((mutable_bitField0_ & 0x00000020) == 0x00000020)) { + if (!((mutable_bitField0_ & 0x00000080) == 0x00000080)) { routes_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000020; + mutable_bitField0_ |= 0x00000080; } routes_.add(input.readUInt32()); break; @@ -35741,9 +35797,9 @@ public final class OsmandOdb { case 130: { int length = input.readRawVarint32(); int limit = input.pushLimit(length); - if (!((mutable_bitField0_ & 0x00000020) == 0x00000020) && input.getBytesUntilLimit() > 0) { + if (!((mutable_bitField0_ & 0x00000080) == 0x00000080) && input.getBytesUntilLimit() > 0) { routes_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000020; + mutable_bitField0_ |= 0x00000080; } while (input.getBytesUntilLimit() > 0) { routes_.add(input.readUInt32()); @@ -35759,7 +35815,10 @@ public final class OsmandOdb { throw new com.google.protobuf.InvalidProtocolBufferException( e.getMessage()).setUnfinishedMessage(this); } finally { - if (((mutable_bitField0_ & 0x00000020) == 0x00000020)) { + if (((mutable_bitField0_ & 0x00000040) == 0x00000040)) { + exits_ = java.util.Collections.unmodifiableList(exits_); + } + if (((mutable_bitField0_ & 0x00000080) == 0x00000080)) { routes_ = java.util.Collections.unmodifiableList(routes_); } this.unknownFields = unknownFields.build(); @@ -35914,6 +35973,66 @@ public final class OsmandOdb { return nameEn_; } + // optional bytes additionalNamePairs = 8; + public static final int ADDITIONALNAMEPAIRS_FIELD_NUMBER = 8; + private com.google.protobuf.ByteString additionalNamePairs_; + /** + * optional bytes additionalNamePairs = 8; + * + *
+     * array of bytes (array of pairs <raw var int>)
+     * 
+ */ + public boolean hasAdditionalNamePairs() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional bytes additionalNamePairs = 8; + * + *
+     * array of bytes (array of pairs <raw var int>)
+     * 
+ */ + public com.google.protobuf.ByteString getAdditionalNamePairs() { + return additionalNamePairs_; + } + + // repeated .OsmAnd.OBF.TransportStopExit exits = 9; + public static final int EXITS_FIELD_NUMBER = 9; + private java.util.List exits_; + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public java.util.List getExitsList() { + return exits_; + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public java.util.List + getExitsOrBuilderList() { + return exits_; + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public int getExitsCount() { + return exits_.size(); + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public net.osmand.binary.OsmandOdb.TransportStopExit getExits(int index) { + return exits_.get(index); + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public net.osmand.binary.OsmandOdb.TransportStopExitOrBuilder getExitsOrBuilder( + int index) { + return exits_.get(index); + } + // repeated uint32 routes = 16; public static final int ROUTES_FIELD_NUMBER = 16; private java.util.List routes_; @@ -35955,6 +36074,8 @@ public final class OsmandOdb { id_ = 0L; name_ = 0; nameEn_ = 0; + additionalNamePairs_ = com.google.protobuf.ByteString.EMPTY; + exits_ = java.util.Collections.emptyList(); routes_ = java.util.Collections.emptyList(); } private byte memoizedIsInitialized = -1; @@ -35978,6 +36099,12 @@ public final class OsmandOdb { memoizedIsInitialized = 0; return false; } + for (int i = 0; i < getExitsCount(); i++) { + if (!getExits(i).isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } memoizedIsInitialized = 1; return true; } @@ -36000,6 +36127,12 @@ public final class OsmandOdb { if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeUInt32(7, nameEn_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeBytes(8, additionalNamePairs_); + } + for (int i = 0; i < exits_.size(); i++) { + output.writeMessage(9, exits_.get(i)); + } for (int i = 0; i < routes_.size(); i++) { output.writeUInt32(16, routes_.get(i)); } @@ -36032,6 +36165,14 @@ public final class OsmandOdb { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(7, nameEn_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(8, additionalNamePairs_); + } + for (int i = 0; i < exits_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(9, exits_.get(i)); + } { int dataSize = 0; for (int i = 0; i < routes_.size(); i++) { @@ -36149,6 +36290,7 @@ public final class OsmandOdb { } private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getExitsFieldBuilder(); } } private static Builder create() { @@ -36167,8 +36309,16 @@ public final class OsmandOdb { bitField0_ = (bitField0_ & ~0x00000008); nameEn_ = 0; bitField0_ = (bitField0_ & ~0x00000010); - routes_ = java.util.Collections.emptyList(); + additionalNamePairs_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000020); + if (exitsBuilder_ == null) { + exits_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000040); + } else { + exitsBuilder_.clear(); + } + routes_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000080); return this; } @@ -36217,9 +36367,22 @@ public final class OsmandOdb { to_bitField0_ |= 0x00000010; } result.nameEn_ = nameEn_; - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } + result.additionalNamePairs_ = additionalNamePairs_; + if (exitsBuilder_ == null) { + if (((bitField0_ & 0x00000040) == 0x00000040)) { + exits_ = java.util.Collections.unmodifiableList(exits_); + bitField0_ = (bitField0_ & ~0x00000040); + } + result.exits_ = exits_; + } else { + result.exits_ = exitsBuilder_.build(); + } + if (((bitField0_ & 0x00000080) == 0x00000080)) { routes_ = java.util.Collections.unmodifiableList(routes_); - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000080); } result.routes_ = routes_; result.bitField0_ = to_bitField0_; @@ -36253,10 +36416,39 @@ public final class OsmandOdb { if (other.hasNameEn()) { setNameEn(other.getNameEn()); } + if (other.hasAdditionalNamePairs()) { + setAdditionalNamePairs(other.getAdditionalNamePairs()); + } + if (exitsBuilder_ == null) { + if (!other.exits_.isEmpty()) { + if (exits_.isEmpty()) { + exits_ = other.exits_; + bitField0_ = (bitField0_ & ~0x00000040); + } else { + ensureExitsIsMutable(); + exits_.addAll(other.exits_); + } + onChanged(); + } + } else { + if (!other.exits_.isEmpty()) { + if (exitsBuilder_.isEmpty()) { + exitsBuilder_.dispose(); + exitsBuilder_ = null; + exits_ = other.exits_; + bitField0_ = (bitField0_ & ~0x00000040); + exitsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getExitsFieldBuilder() : null; + } else { + exitsBuilder_.addAllMessages(other.exits_); + } + } + } if (!other.routes_.isEmpty()) { if (routes_.isEmpty()) { routes_ = other.routes_; - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000080); } else { ensureRoutesIsMutable(); routes_.addAll(other.routes_); @@ -36284,6 +36476,12 @@ public final class OsmandOdb { return false; } + for (int i = 0; i < getExitsCount(); i++) { + if (!getExits(i).isInitialized()) { + + return false; + } + } return true; } @@ -36551,12 +36749,304 @@ public final class OsmandOdb { return this; } + // optional bytes additionalNamePairs = 8; + private com.google.protobuf.ByteString additionalNamePairs_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes additionalNamePairs = 8; + * + *
+       * array of bytes (array of pairs <raw var int>)
+       * 
+ */ + public boolean hasAdditionalNamePairs() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional bytes additionalNamePairs = 8; + * + *
+       * array of bytes (array of pairs <raw var int>)
+       * 
+ */ + public com.google.protobuf.ByteString getAdditionalNamePairs() { + return additionalNamePairs_; + } + /** + * optional bytes additionalNamePairs = 8; + * + *
+       * array of bytes (array of pairs <raw var int>)
+       * 
+ */ + public Builder setAdditionalNamePairs(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000020; + additionalNamePairs_ = value; + onChanged(); + return this; + } + /** + * optional bytes additionalNamePairs = 8; + * + *
+       * array of bytes (array of pairs <raw var int>)
+       * 
+ */ + public Builder clearAdditionalNamePairs() { + bitField0_ = (bitField0_ & ~0x00000020); + additionalNamePairs_ = getDefaultInstance().getAdditionalNamePairs(); + onChanged(); + return this; + } + + // repeated .OsmAnd.OBF.TransportStopExit exits = 9; + private java.util.List exits_ = + java.util.Collections.emptyList(); + private void ensureExitsIsMutable() { + if (!((bitField0_ & 0x00000040) == 0x00000040)) { + exits_ = new java.util.ArrayList(exits_); + bitField0_ |= 0x00000040; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + net.osmand.binary.OsmandOdb.TransportStopExit, net.osmand.binary.OsmandOdb.TransportStopExit.Builder, net.osmand.binary.OsmandOdb.TransportStopExitOrBuilder> exitsBuilder_; + + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public java.util.List getExitsList() { + if (exitsBuilder_ == null) { + return java.util.Collections.unmodifiableList(exits_); + } else { + return exitsBuilder_.getMessageList(); + } + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public int getExitsCount() { + if (exitsBuilder_ == null) { + return exits_.size(); + } else { + return exitsBuilder_.getCount(); + } + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public net.osmand.binary.OsmandOdb.TransportStopExit getExits(int index) { + if (exitsBuilder_ == null) { + return exits_.get(index); + } else { + return exitsBuilder_.getMessage(index); + } + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public Builder setExits( + int index, net.osmand.binary.OsmandOdb.TransportStopExit value) { + if (exitsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureExitsIsMutable(); + exits_.set(index, value); + onChanged(); + } else { + exitsBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public Builder setExits( + int index, net.osmand.binary.OsmandOdb.TransportStopExit.Builder builderForValue) { + if (exitsBuilder_ == null) { + ensureExitsIsMutable(); + exits_.set(index, builderForValue.build()); + onChanged(); + } else { + exitsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public Builder addExits(net.osmand.binary.OsmandOdb.TransportStopExit value) { + if (exitsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureExitsIsMutable(); + exits_.add(value); + onChanged(); + } else { + exitsBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public Builder addExits( + int index, net.osmand.binary.OsmandOdb.TransportStopExit value) { + if (exitsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureExitsIsMutable(); + exits_.add(index, value); + onChanged(); + } else { + exitsBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public Builder addExits( + net.osmand.binary.OsmandOdb.TransportStopExit.Builder builderForValue) { + if (exitsBuilder_ == null) { + ensureExitsIsMutable(); + exits_.add(builderForValue.build()); + onChanged(); + } else { + exitsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public Builder addExits( + int index, net.osmand.binary.OsmandOdb.TransportStopExit.Builder builderForValue) { + if (exitsBuilder_ == null) { + ensureExitsIsMutable(); + exits_.add(index, builderForValue.build()); + onChanged(); + } else { + exitsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public Builder addAllExits( + java.lang.Iterable values) { + if (exitsBuilder_ == null) { + ensureExitsIsMutable(); + super.addAll(values, exits_); + onChanged(); + } else { + exitsBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public Builder clearExits() { + if (exitsBuilder_ == null) { + exits_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000040); + onChanged(); + } else { + exitsBuilder_.clear(); + } + return this; + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public Builder removeExits(int index) { + if (exitsBuilder_ == null) { + ensureExitsIsMutable(); + exits_.remove(index); + onChanged(); + } else { + exitsBuilder_.remove(index); + } + return this; + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public net.osmand.binary.OsmandOdb.TransportStopExit.Builder getExitsBuilder( + int index) { + return getExitsFieldBuilder().getBuilder(index); + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public net.osmand.binary.OsmandOdb.TransportStopExitOrBuilder getExitsOrBuilder( + int index) { + if (exitsBuilder_ == null) { + return exits_.get(index); } else { + return exitsBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public java.util.List + getExitsOrBuilderList() { + if (exitsBuilder_ != null) { + return exitsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(exits_); + } + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public net.osmand.binary.OsmandOdb.TransportStopExit.Builder addExitsBuilder() { + return getExitsFieldBuilder().addBuilder( + net.osmand.binary.OsmandOdb.TransportStopExit.getDefaultInstance()); + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public net.osmand.binary.OsmandOdb.TransportStopExit.Builder addExitsBuilder( + int index) { + return getExitsFieldBuilder().addBuilder( + index, net.osmand.binary.OsmandOdb.TransportStopExit.getDefaultInstance()); + } + /** + * repeated .OsmAnd.OBF.TransportStopExit exits = 9; + */ + public java.util.List + getExitsBuilderList() { + return getExitsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + net.osmand.binary.OsmandOdb.TransportStopExit, net.osmand.binary.OsmandOdb.TransportStopExit.Builder, net.osmand.binary.OsmandOdb.TransportStopExitOrBuilder> + getExitsFieldBuilder() { + if (exitsBuilder_ == null) { + exitsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + net.osmand.binary.OsmandOdb.TransportStopExit, net.osmand.binary.OsmandOdb.TransportStopExit.Builder, net.osmand.binary.OsmandOdb.TransportStopExitOrBuilder>( + exits_, + ((bitField0_ & 0x00000040) == 0x00000040), + getParentForChildren(), + isClean()); + exits_ = null; + } + return exitsBuilder_; + } + // repeated uint32 routes = 16; private java.util.List routes_ = java.util.Collections.emptyList(); private void ensureRoutesIsMutable() { - if (!((bitField0_ & 0x00000020) == 0x00000020)) { + if (!((bitField0_ & 0x00000080) == 0x00000080)) { routes_ = new java.util.ArrayList(routes_); - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000080; } } /** @@ -36640,7 +37130,7 @@ public final class OsmandOdb { */ public Builder clearRoutes() { routes_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000080); onChanged(); return this; } @@ -36656,6 +37146,652 @@ public final class OsmandOdb { // @@protoc_insertion_point(class_scope:OsmAnd.OBF.TransportStop) } + public interface TransportStopExitOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required sint32 dx = 1; + /** + * required sint32 dx = 1; + * + *
+     * delta x
+     * 
+ */ + boolean hasDx(); + /** + * required sint32 dx = 1; + * + *
+     * delta x
+     * 
+ */ + int getDx(); + + // required sint32 dy = 2; + /** + * required sint32 dy = 2; + * + *
+     * delta y
+     * 
+ */ + boolean hasDy(); + /** + * required sint32 dy = 2; + * + *
+     * delta y
+     * 
+ */ + int getDy(); + + // required uint32 ref = 3; + /** + * required uint32 ref = 3; + */ + boolean hasRef(); + /** + * required uint32 ref = 3; + */ + int getRef(); + } + /** + * Protobuf type {@code OsmAnd.OBF.TransportStopExit} + */ + public static final class TransportStopExit extends + com.google.protobuf.GeneratedMessage + implements TransportStopExitOrBuilder { + // Use TransportStopExit.newBuilder() to construct. + private TransportStopExit(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private TransportStopExit(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final TransportStopExit defaultInstance; + public static TransportStopExit getDefaultInstance() { + return defaultInstance; + } + + public TransportStopExit getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private TransportStopExit( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + dx_ = input.readSInt32(); + break; + } + case 16: { + bitField0_ |= 0x00000002; + dy_ = input.readSInt32(); + break; + } + case 24: { + bitField0_ |= 0x00000004; + ref_ = input.readUInt32(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return net.osmand.binary.OsmandOdb.internal_static_OsmAnd_OBF_TransportStopExit_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return net.osmand.binary.OsmandOdb.internal_static_OsmAnd_OBF_TransportStopExit_fieldAccessorTable + .ensureFieldAccessorsInitialized( + net.osmand.binary.OsmandOdb.TransportStopExit.class, net.osmand.binary.OsmandOdb.TransportStopExit.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public TransportStopExit parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new TransportStopExit(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // required sint32 dx = 1; + public static final int DX_FIELD_NUMBER = 1; + private int dx_; + /** + * required sint32 dx = 1; + * + *
+     * delta x
+     * 
+ */ + public boolean hasDx() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required sint32 dx = 1; + * + *
+     * delta x
+     * 
+ */ + public int getDx() { + return dx_; + } + + // required sint32 dy = 2; + public static final int DY_FIELD_NUMBER = 2; + private int dy_; + /** + * required sint32 dy = 2; + * + *
+     * delta y
+     * 
+ */ + public boolean hasDy() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required sint32 dy = 2; + * + *
+     * delta y
+     * 
+ */ + public int getDy() { + return dy_; + } + + // required uint32 ref = 3; + public static final int REF_FIELD_NUMBER = 3; + private int ref_; + /** + * required uint32 ref = 3; + */ + public boolean hasRef() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * required uint32 ref = 3; + */ + public int getRef() { + return ref_; + } + + private void initFields() { + dx_ = 0; + dy_ = 0; + ref_ = 0; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasDx()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasDy()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasRef()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeSInt32(1, dx_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeSInt32(2, dy_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeUInt32(3, ref_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeSInt32Size(1, dx_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeSInt32Size(2, dy_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(3, ref_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static net.osmand.binary.OsmandOdb.TransportStopExit parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static net.osmand.binary.OsmandOdb.TransportStopExit parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static net.osmand.binary.OsmandOdb.TransportStopExit parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static net.osmand.binary.OsmandOdb.TransportStopExit parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static net.osmand.binary.OsmandOdb.TransportStopExit parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static net.osmand.binary.OsmandOdb.TransportStopExit parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static net.osmand.binary.OsmandOdb.TransportStopExit parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static net.osmand.binary.OsmandOdb.TransportStopExit parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static net.osmand.binary.OsmandOdb.TransportStopExit parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static net.osmand.binary.OsmandOdb.TransportStopExit parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(net.osmand.binary.OsmandOdb.TransportStopExit prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code OsmAnd.OBF.TransportStopExit} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements net.osmand.binary.OsmandOdb.TransportStopExitOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return net.osmand.binary.OsmandOdb.internal_static_OsmAnd_OBF_TransportStopExit_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return net.osmand.binary.OsmandOdb.internal_static_OsmAnd_OBF_TransportStopExit_fieldAccessorTable + .ensureFieldAccessorsInitialized( + net.osmand.binary.OsmandOdb.TransportStopExit.class, net.osmand.binary.OsmandOdb.TransportStopExit.Builder.class); + } + + // Construct using net.osmand.binary.OsmandOdb.TransportStopExit.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + dx_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + dy_ = 0; + bitField0_ = (bitField0_ & ~0x00000002); + ref_ = 0; + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return net.osmand.binary.OsmandOdb.internal_static_OsmAnd_OBF_TransportStopExit_descriptor; + } + + public net.osmand.binary.OsmandOdb.TransportStopExit getDefaultInstanceForType() { + return net.osmand.binary.OsmandOdb.TransportStopExit.getDefaultInstance(); + } + + public net.osmand.binary.OsmandOdb.TransportStopExit build() { + net.osmand.binary.OsmandOdb.TransportStopExit result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public net.osmand.binary.OsmandOdb.TransportStopExit buildPartial() { + net.osmand.binary.OsmandOdb.TransportStopExit result = new net.osmand.binary.OsmandOdb.TransportStopExit(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.dx_ = dx_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.dy_ = dy_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.ref_ = ref_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof net.osmand.binary.OsmandOdb.TransportStopExit) { + return mergeFrom((net.osmand.binary.OsmandOdb.TransportStopExit)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(net.osmand.binary.OsmandOdb.TransportStopExit other) { + if (other == net.osmand.binary.OsmandOdb.TransportStopExit.getDefaultInstance()) return this; + if (other.hasDx()) { + setDx(other.getDx()); + } + if (other.hasDy()) { + setDy(other.getDy()); + } + if (other.hasRef()) { + setRef(other.getRef()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasDx()) { + + return false; + } + if (!hasDy()) { + + return false; + } + if (!hasRef()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + net.osmand.binary.OsmandOdb.TransportStopExit parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (net.osmand.binary.OsmandOdb.TransportStopExit) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // required sint32 dx = 1; + private int dx_ ; + /** + * required sint32 dx = 1; + * + *
+       * delta x
+       * 
+ */ + public boolean hasDx() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required sint32 dx = 1; + * + *
+       * delta x
+       * 
+ */ + public int getDx() { + return dx_; + } + /** + * required sint32 dx = 1; + * + *
+       * delta x
+       * 
+ */ + public Builder setDx(int value) { + bitField0_ |= 0x00000001; + dx_ = value; + onChanged(); + return this; + } + /** + * required sint32 dx = 1; + * + *
+       * delta x
+       * 
+ */ + public Builder clearDx() { + bitField0_ = (bitField0_ & ~0x00000001); + dx_ = 0; + onChanged(); + return this; + } + + // required sint32 dy = 2; + private int dy_ ; + /** + * required sint32 dy = 2; + * + *
+       * delta y
+       * 
+ */ + public boolean hasDy() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required sint32 dy = 2; + * + *
+       * delta y
+       * 
+ */ + public int getDy() { + return dy_; + } + /** + * required sint32 dy = 2; + * + *
+       * delta y
+       * 
+ */ + public Builder setDy(int value) { + bitField0_ |= 0x00000002; + dy_ = value; + onChanged(); + return this; + } + /** + * required sint32 dy = 2; + * + *
+       * delta y
+       * 
+ */ + public Builder clearDy() { + bitField0_ = (bitField0_ & ~0x00000002); + dy_ = 0; + onChanged(); + return this; + } + + // required uint32 ref = 3; + private int ref_ ; + /** + * required uint32 ref = 3; + */ + public boolean hasRef() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * required uint32 ref = 3; + */ + public int getRef() { + return ref_; + } + /** + * required uint32 ref = 3; + */ + public Builder setRef(int value) { + bitField0_ |= 0x00000004; + ref_ = value; + onChanged(); + return this; + } + /** + * required uint32 ref = 3; + */ + public Builder clearRef() { + bitField0_ = (bitField0_ & ~0x00000004); + ref_ = 0; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:OsmAnd.OBF.TransportStopExit) + } + + static { + defaultInstance = new TransportStopExit(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:OsmAnd.OBF.TransportStopExit) + } + public interface TransportStopsTreeOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -61027,6 +62163,11 @@ public final class OsmandOdb { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_OsmAnd_OBF_TransportStop_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_OsmAnd_OBF_TransportStopExit_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_OsmAnd_OBF_TransportStopExit_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_OsmAnd_OBF_TransportStopsTree_descriptor; private static @@ -61240,80 +62381,84 @@ public final class OsmandOdb { "ayOfWeekRestriction\030\007 \003(\r\022\034\n\024dayOfYearRe" + "striction\030\010 \003(\r\"W\n\022TransportRouteStop\022\n\n" + "\002id\030\001 \002(\022\022\n\n\002dx\030\002 \002(\021\022\n\n\002dy\030\003 \002(\021\022\014\n\004nam" + - "e\030\006 \002(\r\022\017\n\007name_en\030\007 \001(\r\"b\n\rTransportSto" + - "p\022\n\n\002dx\030\001 \002(\021\022\n\n\002dy\030\002 \002(\021\022\n\n\002id\030\005 \002(\022\022\014\n" + - "\004name\030\006 \002(\r\022\017\n\007name_en\030\007 \001(\r\022\016\n\006routes\030\020" + - " \003(\r\"\272\001\n\022TransportStopsTree\022\014\n\004left\030\001 \002(" + - "\021\022\r\n\005right\030\002 \002(\021\022\013\n\003top\030\003 \002(\021\022\016\n\006bottom\030" + - "\004 \002(\021\0220\n\010subtrees\030\007 \003(\0132\036.OsmAnd.OBF.Tra", - "nsportStopsTree\022(\n\005leafs\030\010 \003(\0132\031.OsmAnd." + - "OBF.TransportStop\022\016\n\006baseId\030\020 \001(\004\"\256\001\n\024Os" + - "mAndTransportIndex\022\014\n\004name\030\001 \001(\t\022+\n\006rout" + - "es\030\003 \001(\0132\033.OsmAnd.OBF.TransportRoutes\022-\n" + - "\005stops\030\006 \001(\0132\036.OsmAnd.OBF.TransportStops" + - "Tree\022,\n\013stringTable\030\t \002(\0132\027.OsmAnd.OBF.S" + - "tringTable\"\312\002\n\016OsmAndPoiIndex\022\014\n\004name\030\001 " + - "\002(\t\022-\n\nboundaries\030\002 \002(\0132\031.OsmAnd.OBF.Osm" + - "AndTileBox\0228\n\017categoriesTable\030\003 \003(\0132\037.Os" + - "mAnd.OBF.OsmAndCategoryTable\0221\n\tnameInde", - "x\030\004 \001(\0132\036.OsmAnd.OBF.OsmAndPoiNameIndex\022" + - "6\n\rsubtypesTable\030\005 \001(\0132\037.OsmAnd.OBF.OsmA" + - "ndSubtypesTable\022\'\n\005boxes\030\006 \003(\0132\030.OsmAnd." + - "OBF.OsmAndPoiBox\022-\n\007poiData\030\t \003(\0132\034.OsmA" + - "nd.OBF.OsmAndPoiBoxData\"\331\001\n\022OsmAndPoiNam" + - "eIndex\022-\n\005table\030\003 \002(\0132\036.OsmAnd.OBF.Index" + - "edStringTable\022C\n\004data\030\005 \003(\01325.OsmAnd.OBF" + - ".OsmAndPoiNameIndex.OsmAndPoiNameIndexDa" + - "ta\032O\n\026OsmAndPoiNameIndexData\0225\n\005atoms\030\003 " + - "\003(\0132&.OsmAnd.OBF.OsmAndPoiNameIndexDataA", - "tom\"Q\n\032OsmAndPoiNameIndexDataAtom\022\014\n\004zoo" + - "m\030\002 \001(\r\022\t\n\001x\030\003 \001(\r\022\t\n\001y\030\004 \001(\r\022\017\n\007shiftTo" + - "\030\016 \001(\007\">\n\023OsmAndCategoryTable\022\020\n\010categor" + - "y\030\001 \002(\t\022\025\n\rsubcategories\030\003 \003(\t\"E\n\023OsmAnd" + - "SubtypesTable\022.\n\010subtypes\030\004 \003(\0132\034.OsmAnd" + - ".OBF.OsmAndPoiSubtype\"\205\001\n\020OsmAndPoiSubty" + - "pe\022\014\n\004name\030\001 \002(\t\022\017\n\007tagname\030\002 \001(\t\022\016\n\006isT" + - "ext\030\003 \002(\010\022\021\n\tfrequency\030\005 \001(\r\022\031\n\021subtypeV" + - "aluesSize\030\006 \001(\r\022\024\n\014subtypeValue\030\010 \003(\t\"\255\001" + - "\n\014OsmAndPoiBox\022\014\n\004zoom\030\001 \002(\r\022\014\n\004left\030\002 \002", - "(\021\022\013\n\003top\030\003 \002(\021\0223\n\ncategories\030\004 \001(\0132\037.Os" + - "mAnd.OBF.OsmAndPoiCategories\022*\n\010subBoxes" + - "\030\n \003(\0132\030.OsmAnd.OBF.OsmAndPoiBox\022\023\n\013shif" + - "tToData\030\016 \001(\007\"@\n\023OsmAndPoiCategories\022\022\n\n" + - "categories\030\003 \003(\r\022\025\n\rsubcategories\030\005 \003(\r\"" + - "i\n\020OsmAndPoiBoxData\022\014\n\004zoom\030\001 \001(\r\022\t\n\001x\030\002" + - " \001(\r\022\t\n\001y\030\003 \001(\r\0221\n\007poiData\030\005 \003(\0132 .OsmAn" + - "d.OBF.OsmAndPoiBoxDataAtom\"\360\001\n\024OsmAndPoi" + - "BoxDataAtom\022\n\n\002dx\030\002 \002(\021\022\n\n\002dy\030\003 \002(\021\022\022\n\nc" + - "ategories\030\004 \003(\r\022\025\n\rsubcategories\030\005 \003(\r\022\014", - "\n\004name\030\006 \001(\t\022\016\n\006nameEn\030\007 \001(\t\022\n\n\002id\030\010 \001(\004" + - "\022\024\n\014openingHours\030\n \001(\t\022\014\n\004site\030\013 \001(\t\022\r\n\005" + - "phone\030\014 \001(\t\022\014\n\004note\030\r \001(\t\022\026\n\016textCategor" + - "ies\030\016 \003(\r\022\022\n\ntextValues\030\017 \003(\t\"\032\n\007IdTable" + - "\022\017\n\007routeId\030\001 \003(\022\"F\n\017RestrictionData\022\014\n\004" + - "type\030\001 \002(\005\022\014\n\004from\030\002 \002(\005\022\n\n\002to\030\003 \002(\005\022\013\n\003" + - "via\030\004 \001(\005\"x\n\tRouteData\022\016\n\006points\030\001 \002(\014\022\022" + - "\n\npointTypes\030\004 \001(\014\022\022\n\npointNames\030\005 \001(\014\022\r" + - "\n\005types\030\007 \002(\014\022\017\n\007routeId\030\014 \002(\005\022\023\n\013string" + - "Names\030\016 \001(\014\"\304\005\n\022OsmAndRoutingIndex\022\014\n\004na", - "me\030\001 \002(\t\022?\n\005rules\030\002 \003(\01320.OsmAnd.OBF.Osm" + - "AndRoutingIndex.RouteEncodingRule\022>\n\troo" + - "tBoxes\030\003 \003(\0132+.OsmAnd.OBF.OsmAndRoutingI" + - "ndex.RouteDataBox\022A\n\014basemapBoxes\030\004 \003(\0132" + - "+.OsmAnd.OBF.OsmAndRoutingIndex.RouteDat" + - "aBox\022=\n\006blocks\030\005 \003(\0132-.OsmAnd.OBF.OsmAnd" + - "RoutingIndex.RouteDataBlock\032;\n\021RouteEnco" + - "dingRule\022\013\n\003tag\030\003 \002(\t\022\r\n\005value\030\005 \002(\t\022\n\n\002" + - "id\030\007 \001(\r\032\231\001\n\014RouteDataBox\022\014\n\004left\030\001 \002(\021\022" + - "\r\n\005right\030\002 \002(\021\022\013\n\003top\030\003 \002(\021\022\016\n\006bottom\030\004 ", - "\002(\021\022\023\n\013shiftToData\030\005 \001(\007\022:\n\005boxes\030\007 \003(\0132" + - "+.OsmAnd.OBF.OsmAndRoutingIndex.RouteDat" + - "aBox\032\303\001\n\016RouteDataBlock\022$\n\007idTable\030\005 \001(\013" + - "2\023.OsmAnd.OBF.IdTable\022*\n\013dataObjects\030\006 \003" + - "(\0132\025.OsmAnd.OBF.RouteData\0221\n\014restriction" + - "s\030\007 \003(\0132\033.OsmAnd.OBF.RestrictionData\022,\n\013" + - "stringTable\030\010 \001(\0132\027.OsmAnd.OBF.StringTab" + - "leB\036\n\021net.osmand.binaryB\tOsmandOdb" + "e\030\006 \002(\r\022\017\n\007name_en\030\007 \001(\r\"\255\001\n\rTransportSt" + + "op\022\n\n\002dx\030\001 \002(\021\022\n\n\002dy\030\002 \002(\021\022\n\n\002id\030\005 \002(\022\022\014" + + "\n\004name\030\006 \002(\r\022\017\n\007name_en\030\007 \001(\r\022\033\n\023additio" + + "nalNamePairs\030\010 \001(\014\022,\n\005exits\030\t \003(\0132\035.OsmA" + + "nd.OBF.TransportStopExit\022\016\n\006routes\030\020 \003(\r" + + "\"8\n\021TransportStopExit\022\n\n\002dx\030\001 \002(\021\022\n\n\002dy\030", + "\002 \002(\021\022\013\n\003ref\030\003 \002(\r\"\272\001\n\022TransportStopsTre" + + "e\022\014\n\004left\030\001 \002(\021\022\r\n\005right\030\002 \002(\021\022\013\n\003top\030\003 " + + "\002(\021\022\016\n\006bottom\030\004 \002(\021\0220\n\010subtrees\030\007 \003(\0132\036." + + "OsmAnd.OBF.TransportStopsTree\022(\n\005leafs\030\010" + + " \003(\0132\031.OsmAnd.OBF.TransportStop\022\016\n\006baseI" + + "d\030\020 \001(\004\"\256\001\n\024OsmAndTransportIndex\022\014\n\004name" + + "\030\001 \001(\t\022+\n\006routes\030\003 \001(\0132\033.OsmAnd.OBF.Tran" + + "sportRoutes\022-\n\005stops\030\006 \001(\0132\036.OsmAnd.OBF." + + "TransportStopsTree\022,\n\013stringTable\030\t \002(\0132" + + "\027.OsmAnd.OBF.StringTable\"\312\002\n\016OsmAndPoiIn", + "dex\022\014\n\004name\030\001 \002(\t\022-\n\nboundaries\030\002 \002(\0132\031." + + "OsmAnd.OBF.OsmAndTileBox\0228\n\017categoriesTa" + + "ble\030\003 \003(\0132\037.OsmAnd.OBF.OsmAndCategoryTab" + + "le\0221\n\tnameIndex\030\004 \001(\0132\036.OsmAnd.OBF.OsmAn" + + "dPoiNameIndex\0226\n\rsubtypesTable\030\005 \001(\0132\037.O" + + "smAnd.OBF.OsmAndSubtypesTable\022\'\n\005boxes\030\006" + + " \003(\0132\030.OsmAnd.OBF.OsmAndPoiBox\022-\n\007poiDat" + + "a\030\t \003(\0132\034.OsmAnd.OBF.OsmAndPoiBoxData\"\331\001" + + "\n\022OsmAndPoiNameIndex\022-\n\005table\030\003 \002(\0132\036.Os" + + "mAnd.OBF.IndexedStringTable\022C\n\004data\030\005 \003(", + "\01325.OsmAnd.OBF.OsmAndPoiNameIndex.OsmAnd" + + "PoiNameIndexData\032O\n\026OsmAndPoiNameIndexDa" + + "ta\0225\n\005atoms\030\003 \003(\0132&.OsmAnd.OBF.OsmAndPoi" + + "NameIndexDataAtom\"Q\n\032OsmAndPoiNameIndexD" + + "ataAtom\022\014\n\004zoom\030\002 \001(\r\022\t\n\001x\030\003 \001(\r\022\t\n\001y\030\004 " + + "\001(\r\022\017\n\007shiftTo\030\016 \001(\007\">\n\023OsmAndCategoryTa" + + "ble\022\020\n\010category\030\001 \002(\t\022\025\n\rsubcategories\030\003" + + " \003(\t\"E\n\023OsmAndSubtypesTable\022.\n\010subtypes\030" + + "\004 \003(\0132\034.OsmAnd.OBF.OsmAndPoiSubtype\"\205\001\n\020" + + "OsmAndPoiSubtype\022\014\n\004name\030\001 \002(\t\022\017\n\007tagnam", + "e\030\002 \001(\t\022\016\n\006isText\030\003 \002(\010\022\021\n\tfrequency\030\005 \001" + + "(\r\022\031\n\021subtypeValuesSize\030\006 \001(\r\022\024\n\014subtype" + + "Value\030\010 \003(\t\"\255\001\n\014OsmAndPoiBox\022\014\n\004zoom\030\001 \002" + + "(\r\022\014\n\004left\030\002 \002(\021\022\013\n\003top\030\003 \002(\021\0223\n\ncategor" + + "ies\030\004 \001(\0132\037.OsmAnd.OBF.OsmAndPoiCategori" + + "es\022*\n\010subBoxes\030\n \003(\0132\030.OsmAnd.OBF.OsmAnd" + + "PoiBox\022\023\n\013shiftToData\030\016 \001(\007\"@\n\023OsmAndPoi" + + "Categories\022\022\n\ncategories\030\003 \003(\r\022\025\n\rsubcat" + + "egories\030\005 \003(\r\"i\n\020OsmAndPoiBoxData\022\014\n\004zoo" + + "m\030\001 \001(\r\022\t\n\001x\030\002 \001(\r\022\t\n\001y\030\003 \001(\r\0221\n\007poiData", + "\030\005 \003(\0132 .OsmAnd.OBF.OsmAndPoiBoxDataAtom" + + "\"\360\001\n\024OsmAndPoiBoxDataAtom\022\n\n\002dx\030\002 \002(\021\022\n\n" + + "\002dy\030\003 \002(\021\022\022\n\ncategories\030\004 \003(\r\022\025\n\rsubcate" + + "gories\030\005 \003(\r\022\014\n\004name\030\006 \001(\t\022\016\n\006nameEn\030\007 \001" + + "(\t\022\n\n\002id\030\010 \001(\004\022\024\n\014openingHours\030\n \001(\t\022\014\n\004" + + "site\030\013 \001(\t\022\r\n\005phone\030\014 \001(\t\022\014\n\004note\030\r \001(\t\022" + + "\026\n\016textCategories\030\016 \003(\r\022\022\n\ntextValues\030\017 " + + "\003(\t\"\032\n\007IdTable\022\017\n\007routeId\030\001 \003(\022\"F\n\017Restr" + + "ictionData\022\014\n\004type\030\001 \002(\005\022\014\n\004from\030\002 \002(\005\022\n" + + "\n\002to\030\003 \002(\005\022\013\n\003via\030\004 \001(\005\"x\n\tRouteData\022\016\n\006", + "points\030\001 \002(\014\022\022\n\npointTypes\030\004 \001(\014\022\022\n\npoin" + + "tNames\030\005 \001(\014\022\r\n\005types\030\007 \002(\014\022\017\n\007routeId\030\014" + + " \002(\005\022\023\n\013stringNames\030\016 \001(\014\"\304\005\n\022OsmAndRout" + + "ingIndex\022\014\n\004name\030\001 \002(\t\022?\n\005rules\030\002 \003(\01320." + + "OsmAnd.OBF.OsmAndRoutingIndex.RouteEncod" + + "ingRule\022>\n\trootBoxes\030\003 \003(\0132+.OsmAnd.OBF." + + "OsmAndRoutingIndex.RouteDataBox\022A\n\014basem" + + "apBoxes\030\004 \003(\0132+.OsmAnd.OBF.OsmAndRouting" + + "Index.RouteDataBox\022=\n\006blocks\030\005 \003(\0132-.Osm" + + "And.OBF.OsmAndRoutingIndex.RouteDataBloc", + "k\032;\n\021RouteEncodingRule\022\013\n\003tag\030\003 \002(\t\022\r\n\005v" + + "alue\030\005 \002(\t\022\n\n\002id\030\007 \001(\r\032\231\001\n\014RouteDataBox\022" + + "\014\n\004left\030\001 \002(\021\022\r\n\005right\030\002 \002(\021\022\013\n\003top\030\003 \002(" + + "\021\022\016\n\006bottom\030\004 \002(\021\022\023\n\013shiftToData\030\005 \001(\007\022:" + + "\n\005boxes\030\007 \003(\0132+.OsmAnd.OBF.OsmAndRouting" + + "Index.RouteDataBox\032\303\001\n\016RouteDataBlock\022$\n" + + "\007idTable\030\005 \001(\0132\023.OsmAnd.OBF.IdTable\022*\n\013d" + + "ataObjects\030\006 \003(\0132\025.OsmAnd.OBF.RouteData\022" + + "1\n\014restrictions\030\007 \003(\0132\033.OsmAnd.OBF.Restr" + + "ictionData\022,\n\013stringTable\030\010 \001(\0132\027.OsmAnd", + ".OBF.StringTableB\036\n\021net.osmand.binaryB\tO" + + "smandOdb" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -61475,27 +62620,33 @@ public final class OsmandOdb { internal_static_OsmAnd_OBF_TransportStop_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_TransportStop_descriptor, - new java.lang.String[] { "Dx", "Dy", "Id", "Name", "NameEn", "Routes", }); - internal_static_OsmAnd_OBF_TransportStopsTree_descriptor = + new java.lang.String[] { "Dx", "Dy", "Id", "Name", "NameEn", "AdditionalNamePairs", "Exits", "Routes", }); + internal_static_OsmAnd_OBF_TransportStopExit_descriptor = getDescriptor().getMessageTypes().get(21); + internal_static_OsmAnd_OBF_TransportStopExit_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_OsmAnd_OBF_TransportStopExit_descriptor, + new java.lang.String[] { "Dx", "Dy", "Ref", }); + internal_static_OsmAnd_OBF_TransportStopsTree_descriptor = + getDescriptor().getMessageTypes().get(22); internal_static_OsmAnd_OBF_TransportStopsTree_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_TransportStopsTree_descriptor, new java.lang.String[] { "Left", "Right", "Top", "Bottom", "Subtrees", "Leafs", "BaseId", }); internal_static_OsmAnd_OBF_OsmAndTransportIndex_descriptor = - getDescriptor().getMessageTypes().get(22); + getDescriptor().getMessageTypes().get(23); internal_static_OsmAnd_OBF_OsmAndTransportIndex_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndTransportIndex_descriptor, new java.lang.String[] { "Name", "Routes", "Stops", "StringTable", }); internal_static_OsmAnd_OBF_OsmAndPoiIndex_descriptor = - getDescriptor().getMessageTypes().get(23); + getDescriptor().getMessageTypes().get(24); internal_static_OsmAnd_OBF_OsmAndPoiIndex_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndPoiIndex_descriptor, new java.lang.String[] { "Name", "Boundaries", "CategoriesTable", "NameIndex", "SubtypesTable", "Boxes", "PoiData", }); internal_static_OsmAnd_OBF_OsmAndPoiNameIndex_descriptor = - getDescriptor().getMessageTypes().get(24); + getDescriptor().getMessageTypes().get(25); internal_static_OsmAnd_OBF_OsmAndPoiNameIndex_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndPoiNameIndex_descriptor, @@ -61507,73 +62658,73 @@ public final class OsmandOdb { internal_static_OsmAnd_OBF_OsmAndPoiNameIndex_OsmAndPoiNameIndexData_descriptor, new java.lang.String[] { "Atoms", }); internal_static_OsmAnd_OBF_OsmAndPoiNameIndexDataAtom_descriptor = - getDescriptor().getMessageTypes().get(25); + getDescriptor().getMessageTypes().get(26); internal_static_OsmAnd_OBF_OsmAndPoiNameIndexDataAtom_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndPoiNameIndexDataAtom_descriptor, new java.lang.String[] { "Zoom", "X", "Y", "ShiftTo", }); internal_static_OsmAnd_OBF_OsmAndCategoryTable_descriptor = - getDescriptor().getMessageTypes().get(26); + getDescriptor().getMessageTypes().get(27); internal_static_OsmAnd_OBF_OsmAndCategoryTable_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndCategoryTable_descriptor, new java.lang.String[] { "Category", "Subcategories", }); internal_static_OsmAnd_OBF_OsmAndSubtypesTable_descriptor = - getDescriptor().getMessageTypes().get(27); + getDescriptor().getMessageTypes().get(28); internal_static_OsmAnd_OBF_OsmAndSubtypesTable_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndSubtypesTable_descriptor, new java.lang.String[] { "Subtypes", }); internal_static_OsmAnd_OBF_OsmAndPoiSubtype_descriptor = - getDescriptor().getMessageTypes().get(28); + getDescriptor().getMessageTypes().get(29); internal_static_OsmAnd_OBF_OsmAndPoiSubtype_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndPoiSubtype_descriptor, new java.lang.String[] { "Name", "Tagname", "IsText", "Frequency", "SubtypeValuesSize", "SubtypeValue", }); internal_static_OsmAnd_OBF_OsmAndPoiBox_descriptor = - getDescriptor().getMessageTypes().get(29); + getDescriptor().getMessageTypes().get(30); internal_static_OsmAnd_OBF_OsmAndPoiBox_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndPoiBox_descriptor, new java.lang.String[] { "Zoom", "Left", "Top", "Categories", "SubBoxes", "ShiftToData", }); internal_static_OsmAnd_OBF_OsmAndPoiCategories_descriptor = - getDescriptor().getMessageTypes().get(30); + getDescriptor().getMessageTypes().get(31); internal_static_OsmAnd_OBF_OsmAndPoiCategories_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndPoiCategories_descriptor, new java.lang.String[] { "Categories", "Subcategories", }); internal_static_OsmAnd_OBF_OsmAndPoiBoxData_descriptor = - getDescriptor().getMessageTypes().get(31); + getDescriptor().getMessageTypes().get(32); internal_static_OsmAnd_OBF_OsmAndPoiBoxData_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndPoiBoxData_descriptor, new java.lang.String[] { "Zoom", "X", "Y", "PoiData", }); internal_static_OsmAnd_OBF_OsmAndPoiBoxDataAtom_descriptor = - getDescriptor().getMessageTypes().get(32); + getDescriptor().getMessageTypes().get(33); internal_static_OsmAnd_OBF_OsmAndPoiBoxDataAtom_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndPoiBoxDataAtom_descriptor, new java.lang.String[] { "Dx", "Dy", "Categories", "Subcategories", "Name", "NameEn", "Id", "OpeningHours", "Site", "Phone", "Note", "TextCategories", "TextValues", }); internal_static_OsmAnd_OBF_IdTable_descriptor = - getDescriptor().getMessageTypes().get(33); + getDescriptor().getMessageTypes().get(34); internal_static_OsmAnd_OBF_IdTable_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_IdTable_descriptor, new java.lang.String[] { "RouteId", }); internal_static_OsmAnd_OBF_RestrictionData_descriptor = - getDescriptor().getMessageTypes().get(34); + getDescriptor().getMessageTypes().get(35); internal_static_OsmAnd_OBF_RestrictionData_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_RestrictionData_descriptor, new java.lang.String[] { "Type", "From", "To", "Via", }); internal_static_OsmAnd_OBF_RouteData_descriptor = - getDescriptor().getMessageTypes().get(35); + getDescriptor().getMessageTypes().get(36); internal_static_OsmAnd_OBF_RouteData_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_RouteData_descriptor, new java.lang.String[] { "Points", "PointTypes", "PointNames", "Types", "RouteId", "StringNames", }); internal_static_OsmAnd_OBF_OsmAndRoutingIndex_descriptor = - getDescriptor().getMessageTypes().get(36); + getDescriptor().getMessageTypes().get(37); internal_static_OsmAnd_OBF_OsmAndRoutingIndex_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_OsmAnd_OBF_OsmAndRoutingIndex_descriptor, diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/RouteDataObject.java b/OsmAnd-java/src/main/java/net/osmand/binary/RouteDataObject.java index a1e10eae7a..5cbb1cd0cc 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/RouteDataObject.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/RouteDataObject.java @@ -578,7 +578,21 @@ public class RouteDataObject { public boolean loop(){ return pointsX[0] == pointsX[pointsX.length - 1] && pointsY[0] == pointsY[pointsY.length - 1] ; } - + + public boolean platform(){ + int sz = types.length; + for(int i=0; i 0) { + JSONObject additionalInfoObj = new JSONObject(); + for (Entry e : additionalInfo.entrySet()) { + additionalInfoObj.put(e.getKey(), e.getValue()); + } + json.put("additionalInfo", additionalInfoObj); + } + + return json; + } + + public static Amenity parseJSON(JSONObject json) { + Amenity a = new Amenity(); + MapObject.parseJSON(json, a); + + if (json.has("subType")) { + a.subType = json.getString("subType"); + } + if (json.has("type")) { + String categoryName = json.getString("type"); + a.setType(MapPoiTypes.getDefault().getPoiCategoryByName(categoryName)); + } else { + a.setType(MapPoiTypes.getDefault().getOtherPoiCategory()); + } + if (json.has("openingHours")) { + a.openingHours = json.getString("openingHours"); + } + if (json.has("additionalInfo")) { + JSONObject namesObj = json.getJSONObject("additionalInfo"); + a.additionalInfo = new HashMap<>(); + Iterator iterator = namesObj.keys(); + while (iterator.hasNext()) { + String key = iterator.next(); + String value = namesObj.getString(key); + a.additionalInfo.put(key, value); + } + } + return a; + } } diff --git a/OsmAnd-java/src/main/java/net/osmand/data/Building.java b/OsmAnd-java/src/main/java/net/osmand/data/Building.java index a025e755ff..7668d16a30 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/Building.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/Building.java @@ -1,12 +1,15 @@ package net.osmand.data; +import net.osmand.util.Algorithms; + +import org.json.JSONObject; + import java.util.Collections; import java.util.LinkedHashMap; +import java.util.Locale; import java.util.Map; -import net.osmand.util.Algorithms; - public class Building extends MapObject { private String postcode; @@ -216,4 +219,43 @@ public class Building extends MapObject { return ""; } + public JSONObject toJSON() { + JSONObject json = super.toJSON(); + json.put("postcode", postcode); + if (latLon2 != null) { + json.put("lat2", String.format(Locale.US, "%.5f", latLon2.getLatitude())); + json.put("lon2", String.format(Locale.US, "%.5f", latLon2.getLongitude())); + } + if (interpolationType != null) { + json.put("interpolationType", interpolationType.name()); + } + if (interpolationInterval != 0) { + json.put("interpolationInterval", interpolationInterval); + } + json.put("name2", name2); + + return json; + } + + public static Building parseJSON(JSONObject json) throws IllegalArgumentException { + Building b = new Building(); + MapObject.parseJSON(json, b); + + if (json.has("postcode")) { + b.postcode = json.getString("postcode"); + } + if (json.has("lat2") && json.has("lon2")) { + b.latLon2 = new LatLon(json.getDouble("lat2"), json.getDouble("lon2")); + } + if (json.has("interpolationType")) { + b.interpolationType = BuildingInterpolation.valueOf(json.getString("interpolationType")); + } + if (json.has("interpolationInterval")) { + b.interpolationInterval = json.getInt("interpolationInterval"); + } + if (json.has("name2")) { + b.name2 = json.getString("name2"); + } + return b; + } } diff --git a/OsmAnd-java/src/main/java/net/osmand/data/City.java b/OsmAnd-java/src/main/java/net/osmand/data/City.java index 264fc455b6..d534d1a63b 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/City.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/City.java @@ -1,5 +1,8 @@ package net.osmand.data; +import org.json.JSONArray; +import org.json.JSONObject; + import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -55,7 +58,6 @@ public class City extends MapObject { return new City(postcode, POSTCODE_INTERNAL_ID--); } - public City(CityType type) { if (type == null) { throw new NullPointerException(); @@ -156,4 +158,47 @@ public class City extends MapObject { return m; } + public JSONObject toJSON() { + return toJSON(true); + } + + public JSONObject toJSON(boolean includingBuildings) { + JSONObject json = super.toJSON(); + json.put("type", type.name()); + json.put("postcode", postcode); + JSONArray listOfStreetsArr = new JSONArray(); + for (Street s : listOfStreets) { + listOfStreetsArr.put(s.toJSON(includingBuildings)); + } + json.put("listOfStreets", listOfStreetsArr); + + return json; + } + + public static City parseJSON(JSONObject json) throws IllegalArgumentException { + CityType type; + if (json.has("type")) { + type = CityType.valueOf(json.getString("type")); + } else { + throw new IllegalArgumentException(); + } + City c = new City(type); + MapObject.parseJSON(json, c); + + if (json.has("postcode")) { + c.postcode = json.getString("postcode"); + } + if (json.has("listOfStreets")) { + JSONArray streetsArr = json.getJSONArray("listOfStreets"); + c.listOfStreets = new ArrayList<>(); + for (int i = 0; i < streetsArr.length(); i++) { + JSONObject streetObj = streetsArr.getJSONObject(i); + Street street = Street.parseJSON(c, streetObj); + if (street != null) { + c.listOfStreets.add(street); + } + } + } + return c; + } } diff --git a/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java b/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java index 8c345239d6..f59fb006ab 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/MapObject.java @@ -1,19 +1,23 @@ package net.osmand.data; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - import net.osmand.Collator; import net.osmand.OsmAndCollator; import net.osmand.util.Algorithms; import net.sf.junidecode.Junidecode; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + public abstract class MapObject implements Comparable { @@ -73,6 +77,15 @@ public abstract class MapObject implements Comparable { } } + public void setNames(Map name) { + if (name != null) { + if (names == null) { + names = new HashMap(); + } + names.putAll(name); + } + } + public Map getNamesMap(boolean includeEn) { if (!includeEn || Algorithms.isEmpty(enName)) { if (names == null) { @@ -289,4 +302,48 @@ public abstract class MapObject implements Comparable { return referenceFile; } + public JSONObject toJSON() { + JSONObject json = new JSONObject(); + json.put("name", name); + json.put("enName", enName); + if (names != null && names.size() > 0) { + JSONObject namesObj = new JSONObject(); + for (Entry e : names.entrySet()) { + namesObj.put(e.getKey(), e.getValue()); + } + json.put("names", namesObj); + } + if (location != null) { + json.put("lat", String.format(Locale.US, "%.5f", location.getLatitude())); + json.put("lon", String.format(Locale.US, "%.5f", location.getLongitude())); + } + json.put("id", id); + + return json; + } + + protected static void parseJSON(JSONObject json, MapObject o) { + if (json.has("name")) { + o.name = json.getString("name"); + } + if (json.has("enName")) { + o.enName = json.getString("enName"); + } + if (json.has("names")) { + JSONObject namesObj = json.getJSONObject("names"); + o.names = new HashMap<>(); + Iterator iterator = namesObj.keys(); + while (iterator.hasNext()) { + String key = iterator.next(); + String value = namesObj.getString(key); + o.names.put(key, value); + } + } + if (json.has("lat") && json.has("lon")) { + o.location = new LatLon(json.getDouble("lat"), json.getDouble("lon")); + } + if (json.has("id")) { + o.id = json.getLong("id"); + } + } } diff --git a/OsmAnd-java/src/main/java/net/osmand/data/Street.java b/OsmAnd-java/src/main/java/net/osmand/data/Street.java index 7e45370578..7ccfa3d8b0 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/Street.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/Street.java @@ -1,12 +1,15 @@ package net.osmand.data; +import net.osmand.util.Algorithms; + +import org.json.JSONArray; +import org.json.JSONObject; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import net.osmand.util.Algorithms; - public class Street extends MapObject { @@ -86,4 +89,57 @@ public class Street extends MapObject { } return nm; } + + public JSONObject toJSON() { + return toJSON(true); + } + + public JSONObject toJSON(boolean includingBuildings) { + JSONObject json = super.toJSON(); + if (buildings.size() > 0 && includingBuildings) { + JSONArray buildingsArr = new JSONArray(); + for (Building b : buildings) { + buildingsArr.put(b.toJSON()); + } + json.put("buildings", buildingsArr); + } + if (intersectedStreets != null) { + JSONArray intersectedStreetsArr = new JSONArray(); + for (Street s : intersectedStreets) { + intersectedStreetsArr.put(s.toJSON()); + } + json.put("intersectedStreets", intersectedStreetsArr); + } + + return json; + } + + public static Street parseJSON(City city, JSONObject json) throws IllegalArgumentException { + Street s = new Street(city); + MapObject.parseJSON(json, s); + + if (json.has("buildings")) { + JSONArray buildingsArr = json.getJSONArray("buildings"); + s.buildings = new ArrayList<>(); + for (int i = 0; i < buildingsArr.length(); i++) { + JSONObject buildingObj = buildingsArr.getJSONObject(i); + Building building = Building.parseJSON(buildingObj); + if (building != null) { + s.buildings.add(building); + } + } + } + if (json.has("intersectedStreets")) { + JSONArray streetsArr = json.getJSONArray("intersectedStreets"); + s.intersectedStreets = new ArrayList<>(); + for (int i = 0; i < streetsArr.length(); i++) { + JSONObject streetObj = streetsArr.getJSONObject(i); + Street street = parseJSON(city, streetObj); + if (street != null) { + s.intersectedStreets.add(street); + } + } + } + return s; + } } diff --git a/OsmAnd-java/src/main/java/net/osmand/data/TransportRoute.java b/OsmAnd-java/src/main/java/net/osmand/data/TransportRoute.java index 70997955f5..d995c811e4 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/TransportRoute.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/TransportRoute.java @@ -1,5 +1,9 @@ package net.osmand.data; +import net.osmand.osm.edit.Node; +import net.osmand.osm.edit.Way; +import net.osmand.util.MapUtils; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -8,10 +12,6 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import net.osmand.osm.edit.Node; -import net.osmand.osm.edit.Way; -import net.osmand.util.MapUtils; - public class TransportRoute extends MapObject { private List forwardStops = new ArrayList(); private String ref; @@ -209,4 +209,16 @@ public class TransportRoute extends MapObject { return d; } + public String getAdjustedRouteRef() { + if (ref != null) { + int charPos = ref.lastIndexOf(':'); + if (charPos != -1) { + ref = ref.substring(0, charPos); + } + if (ref.length() > 4) { + ref = ref.substring(0, 4); + } + } + return ref; + } } \ No newline at end of file diff --git a/OsmAnd-java/src/main/java/net/osmand/data/TransportStop.java b/OsmAnd-java/src/main/java/net/osmand/data/TransportStop.java index 981b182a63..eeaad172cf 100644 --- a/OsmAnd-java/src/main/java/net/osmand/data/TransportStop.java +++ b/OsmAnd-java/src/main/java/net/osmand/data/TransportStop.java @@ -2,13 +2,20 @@ package net.osmand.data; import net.osmand.util.MapUtils; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class TransportStop extends MapObject { private int[] referencesToRoutes = null; private Amenity amenity; public int distance; public int x31; public int y31; - + private List exits; + private HashMap names; public TransportStop(){ } @@ -39,4 +46,35 @@ public class TransportStop extends MapObject { y31 = dy << (31 - zoom); setLocation(MapUtils.getLatitudeFromTile(zoom, dy), MapUtils.getLongitudeFromTile(zoom, dx)); } + + public void addExit(TransportStopExit transportStopExit) { + if (exits == null) { + exits = new ArrayList<>(); + } + exits.add(transportStopExit); + } + + public List getExits () { + if (exits == null) { + return Collections.emptyList(); + } + return this.exits; + } + + public String getExitsString () { + String exitsString = ""; + String refString = ""; + if (this.exits != null) { + int i = 1; + exitsString = exitsString + " Exits: ["; + for (TransportStopExit e : this.exits ) { + if (e.getRef() != null) { + refString = " [ref:" + e.getRef() + "] "; + } + exitsString = exitsString + " " + i + ")" + refString + e.getName() + " " + e.getLocation() + " ]"; + i++; + } + } + return exitsString; + } } diff --git a/OsmAnd-java/src/main/java/net/osmand/data/TransportStopExit.java b/OsmAnd-java/src/main/java/net/osmand/data/TransportStopExit.java new file mode 100644 index 0000000000..2c4d6a2a68 --- /dev/null +++ b/OsmAnd-java/src/main/java/net/osmand/data/TransportStopExit.java @@ -0,0 +1,27 @@ +package net.osmand.data; + +import net.osmand.util.MapUtils; + +public class TransportStopExit extends MapObject { + public int x31; + public int y31; + public String ref = null; + @Override + public void setLocation(double latitude, double longitude) { + super.setLocation(latitude, longitude); + } + public void setLocation(int zoom, int dx, int dy) { + x31 = dx << (31 - zoom); + y31 = dy << (31 - zoom); + setLocation(MapUtils.getLatitudeFromTile(zoom, dy), MapUtils.getLongitudeFromTile(zoom, dx)); + } + public void setRef (String ref) { + this.ref = ref; + } + public String getRef() { + if (ref != null) { + return ref; + } + return ""; + } +} diff --git a/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java b/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java index d464f881f4..409b188b93 100644 --- a/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java +++ b/OsmAnd-java/src/main/java/net/osmand/map/TileSourceManager.java @@ -435,7 +435,6 @@ public class TileSourceManager { public static java.util.List getKnownSourceTemplates() { java.util.List list = new ArrayList(); list.add(getMapnikSource()); - list.add(getCycleMapSource()); list.add(getMapillaryRasterSource()); list.add(getMapillaryVectorSource()); return list; @@ -445,9 +444,6 @@ public class TileSourceManager { return MAPNIK_SOURCE; } - public static TileSourceTemplate getCycleMapSource(){ - return CYCLE_MAP_SOURCE; - } public static TileSourceTemplate getMapillaryRasterSource() { return MAPILLARY_RASTER_SOURCE; diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/edit/OSMSettings.java b/OsmAnd-java/src/main/java/net/osmand/osm/edit/OSMSettings.java index fd506c7291..87b9c755a1 100644 --- a/OsmAnd-java/src/main/java/net/osmand/osm/edit/OSMSettings.java +++ b/OsmAnd-java/src/main/java/net/osmand/osm/edit/OSMSettings.java @@ -13,6 +13,7 @@ public class OSMSettings { BOUNDARY("boundary"), //$NON-NLS-1$ POSTAL_CODE("postal_code"), //$NON-NLS-1$ RAILWAY("railway"), //$NON-NLS-1$ + STATION("subway"), //$NON-NLS-1$ ONEWAY("oneway"), //$NON-NLS-1$ LAYER("layer"), //$NON-NLS-1$ BRIDGE("bridge"), //$NON-NLS-1$ diff --git a/OsmAnd-java/src/main/java/net/osmand/osm/edit/Way.java b/OsmAnd-java/src/main/java/net/osmand/osm/edit/Way.java index f55667b1ed..ba71f7645a 100644 --- a/OsmAnd-java/src/main/java/net/osmand/osm/edit/Way.java +++ b/OsmAnd-java/src/main/java/net/osmand/osm/edit/Way.java @@ -194,7 +194,6 @@ public class Way extends Entity { } if (nodeIds != null) { nodeIds.reverse(); - ; } } } diff --git a/OsmAnd-java/src/main/java/net/osmand/router/GeneralRouter.java b/OsmAnd-java/src/main/java/net/osmand/router/GeneralRouter.java index b1ebbc9b30..e195c42d42 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/GeneralRouter.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/GeneralRouter.java @@ -32,6 +32,7 @@ public class GeneralRouter implements VehicleRouter { public static final String AVOID_UNPAVED = "avoid_unpaved"; public static final String PREFER_MOTORWAYS = "prefer_motorway"; public static final String ALLOW_PRIVATE = "allow_private"; + public static final String ALLOW_MOTORWAYS = "allow_motorway"; private final RouteAttributeContext[] objectAttributes; public final Map attributes; diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java b/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java index cad29d75e3..c2d5776de4 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/RoutePlannerFrontEnd.java @@ -55,6 +55,10 @@ public class RoutePlannerFrontEnd { } public RouteSegmentPoint findRouteSegment(double lat, double lon, RoutingContext ctx, List list) throws IOException { + return findRouteSegment(lat, lon, ctx, list, false); + } + + public RouteSegmentPoint findRouteSegment(double lat, double lon, RoutingContext ctx, List list, boolean transportStop) throws IOException { int px = MapUtils.get31TileNumberX(lon); int py = MapUtils.get31TileNumberY(lat); ArrayList dataObjects = new ArrayList(); @@ -92,7 +96,26 @@ public class RoutePlannerFrontEnd { } }); if (list.size() > 0) { - RouteSegmentPoint ps = list.get(0); + RouteSegmentPoint ps = null; + if (ctx.publicTransport) { + for (RouteSegmentPoint p : list) { + if (transportStop && p.distSquare > 100) { + break; + } + boolean platform = p.road.platform(); + if (transportStop && platform) { + ps = p; + break; + } + if (!transportStop && !platform) { + ps = p; + break; + } + } + } + if (ps == null) { + ps = list.get(0); + } ps.others = list; return ps; } @@ -185,17 +208,17 @@ public class RoutePlannerFrontEnd { } int indexNotFound = 0; List points = new ArrayList(); - if (!addSegment(start, ctx, indexNotFound++, points)) { + if (!addSegment(start, ctx, indexNotFound++, points, ctx.startTransportStop)) { return null; } if (intermediates != null) { for (LatLon l : intermediates) { - if (!addSegment(l, ctx, indexNotFound++, points)) { + if (!addSegment(l, ctx, indexNotFound++, points, false)) { return null; } } } - if (!addSegment(end, ctx, indexNotFound++, points)) { + if (!addSegment(end, ctx, indexNotFound++, points, ctx.targetTransportStop)) { return null; } ctx.calculationProgress.nextIteration(); @@ -315,8 +338,8 @@ public class RoutePlannerFrontEnd { } - private boolean addSegment(LatLon s, RoutingContext ctx, int indexNotFound, List res) throws IOException { - RouteSegmentPoint f = findRouteSegment(s.getLatitude(), s.getLongitude(), ctx, null); + private boolean addSegment(LatLon s, RoutingContext ctx, int indexNotFound, List res, boolean transportStop) throws IOException { + RouteSegmentPoint f = findRouteSegment(s.getLatitude(), s.getLongitude(), ctx, null, transportStop); if (f == null) { ctx.calculationProgress.segmentNotFound = indexNotFound; return false; @@ -405,7 +428,8 @@ public class RoutePlannerFrontEnd { long time = System.currentTimeMillis(); RouteSegmentResult[] res = ctx.nativeLib.runNativeRouting(ctx.startX, ctx.startY, ctx.targetX, ctx.targetY, - ctx.config, regions, ctx.calculationProgress, ctx.precalculatedRouteDirection, ctx.calculationMode == RouteCalculationMode.BASE); + ctx.config, regions, ctx.calculationProgress, ctx.precalculatedRouteDirection, ctx.calculationMode == RouteCalculationMode.BASE, + ctx.publicTransport, ctx.startTransportStop, ctx.targetTransportStop); log.info("Native routing took " + (System.currentTimeMillis() - time) / 1000f + " seconds"); ArrayList result = new ArrayList(Arrays.asList(res)); if (recalculationEnd != null) { diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java b/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java index 46996428e1..167dfbebe9 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/RouteResultPreparation.java @@ -41,7 +41,6 @@ public class RouteResultPreparation { */ List prepareResult(RoutingContext ctx, FinalRouteSegment finalSegment) throws IOException { List result = convertFinalSegmentToResults(ctx, finalSegment); - combineWayPointsForAreaRouting(ctx, result); prepareResult(ctx, result); return result; } @@ -158,6 +157,7 @@ public class RouteResultPreparation { } List prepareResult(RoutingContext ctx, List result) throws IOException { + combineWayPointsForAreaRouting(ctx, result); validateAllPointsConnected(result); splitRoadsAndAttachRoadSegments(ctx, result); calculateTimeSpeed(ctx, result); diff --git a/OsmAnd-java/src/main/java/net/osmand/router/RoutingContext.java b/OsmAnd-java/src/main/java/net/osmand/router/RoutingContext.java index ddd44e738c..91a4fd0eff 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/RoutingContext.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/RoutingContext.java @@ -57,8 +57,11 @@ public class RoutingContext { // 1. Initial variables public int startX; public int startY; + public boolean startTransportStop; public int targetX; public int targetY; + public boolean targetTransportStop; + public boolean publicTransport; // deprecated public long firstRoadId; public int firstRoadDirection; diff --git a/OsmAnd-java/src/main/java/net/osmand/router/TransportRoutePlanner.java b/OsmAnd-java/src/main/java/net/osmand/router/TransportRoutePlanner.java index d93665ffca..8d022254d5 100644 --- a/OsmAnd-java/src/main/java/net/osmand/router/TransportRoutePlanner.java +++ b/OsmAnd-java/src/main/java/net/osmand/router/TransportRoutePlanner.java @@ -1,19 +1,5 @@ package net.osmand.router; -import gnu.trove.iterator.TIntIterator; -import gnu.trove.list.array.TIntArrayList; -import gnu.trove.map.hash.TIntObjectHashMap; -import gnu.trove.map.hash.TLongObjectHashMap; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.PriorityQueue; - import net.osmand.binary.BinaryMapIndexReader; import net.osmand.binary.BinaryMapIndexReader.SearchRequest; import net.osmand.data.LatLon; @@ -24,11 +10,25 @@ import net.osmand.osm.edit.Node; import net.osmand.osm.edit.Way; import net.osmand.util.MapUtils; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; + +import gnu.trove.iterator.TIntIterator; +import gnu.trove.list.array.TIntArrayList; +import gnu.trove.map.hash.TIntObjectHashMap; +import gnu.trove.map.hash.TLongObjectHashMap; + public class TransportRoutePlanner { - public List buildRoute(TransportRoutingContext ctx, LatLon start, LatLon end) throws IOException { + public List buildRoute(TransportRoutingContext ctx, LatLon start, LatLon end) throws IOException, InterruptedException { ctx.startCalcTime = System.currentTimeMillis(); List startStops = ctx.getTransportStops(start); List endStops = ctx.getTransportStops(end); @@ -45,8 +45,11 @@ public class TransportRoutePlanner { } double finishTime = ctx.cfg.maxRouteTime; List results = new ArrayList(); - + initProgressBar(ctx, start, end); while (!queue.isEmpty()) { + if (ctx.calculationProgress != null && ctx.calculationProgress.isCancelled) { + return null; + } TransportRouteSegment segment = queue.poll(); TransportRouteSegment ex = ctx.visitedSegments.get(segment.getId()); if(ex != null) { @@ -56,7 +59,6 @@ public class TransportRoutePlanner { continue; } ctx.visitedRoutesCount++; - System.out.println(segment); ctx.visitedSegments.put(segment.getId(), segment); if (segment.getDepth() > ctx.cfg.maxNumberOfChanges) { continue; @@ -72,7 +74,10 @@ public class TransportRoutePlanner { TransportStop prevStop = segment.getStop(segment.segStart); List sgms = new ArrayList(); for (int ind = 1 + segment.segStart; ind < segment.getLength(); ind++) { - segmentId ++; + if (ctx.calculationProgress != null && ctx.calculationProgress.isCancelled) { + return null; + } + segmentId ++; ctx.visitedSegments.put(segmentId, segment); TransportStop stop = segment.getStop(ind); // could be geometry size @@ -88,6 +93,9 @@ public class TransportRoutePlanner { sgms.clear(); sgms = ctx.getTransportStops(stop.x31, stop.y31, true, sgms); for (TransportRouteSegment sgm : sgms) { + if (ctx.calculationProgress != null && ctx.calculationProgress.isCancelled) { + return null; + } if (segment.wasVisited(sgm)) { continue; } @@ -137,10 +145,36 @@ public class TransportRoutePlanner { results.add(finish); } } + + if (ctx.calculationProgress != null && ctx.calculationProgress.isCancelled) { + throw new InterruptedException("Route calculation interrupted"); + } + updateCalculationProgress(ctx, queue); + } return prepareResults(ctx, results); } + + private void initProgressBar(TransportRoutingContext ctx, LatLon start, LatLon end) { + ctx.calculationProgress.distanceFromEnd = 0; + ctx.calculationProgress.reverseSegmentQueueSize = 0; + ctx.calculationProgress.directSegmentQueueSize = 0; + float speed = (float) ctx.cfg.travelSpeed + 1; // assume + ctx.calculationProgress.totalEstimatedDistance = (float) (MapUtils.getDistance(start, end)/ speed); + } + + private void updateCalculationProgress(TransportRoutingContext ctx, PriorityQueue queue) { + if (ctx.calculationProgress != null) { + ctx.calculationProgress.directSegmentQueueSize = queue.size(); + if (queue.size() > 0) { + TransportRouteSegment peek = queue.peek(); + ctx.calculationProgress.distanceFromBegin = (float) Math.max(peek.distFromStart, + ctx.calculationProgress.distanceFromBegin); + } + } + } + private List prepareResults(TransportRoutingContext ctx, List results) { Collections.sort(results, new SegmentsComparator(ctx)); @@ -149,11 +183,17 @@ public class TransportRoutePlanner { (System.currentTimeMillis() - ctx.startCalcTime) / 1000.0, results.size(), ctx.visitedRoutesCount, ctx.quadTree.size(), ctx.readTime / (1000 * 1000), ctx.loadTime / (1000 * 1000))); for(TransportRouteSegment res : results) { + if (ctx.calculationProgress != null && ctx.calculationProgress.isCancelled) { + return null; + } TransportRouteResult route = new TransportRouteResult(ctx); route.routeTime = res.distFromStart; route.finishWalkDist = res.walkDist; TransportRouteSegment p = res; while (p != null) { + if (ctx.calculationProgress != null && ctx.calculationProgress.isCancelled) { + return null; + } if (p.parentRoute != null) { TransportRouteResultSegment sg = new TransportRouteResultSegment(p.parentRoute.road, p.parentRoute.segStart, p.parentStop, p.parentRoute.walkDist, @@ -165,6 +205,9 @@ public class TransportRoutePlanner { // test if faster routes fully included boolean include = false; for(TransportRouteResult s : lst) { + if (ctx.calculationProgress != null && ctx.calculationProgress.isCancelled) { + return null; + } if(includeRoute(s, route)) { include = true; break; @@ -255,7 +298,16 @@ public class TransportRoutePlanner { public TransportStop getEnd() { return route.getForwardStops().get(end); } - + + public List getNodes() { + List nodes = new ArrayList<>(); + List ways = getGeometry(); + for (Way way : ways) { + nodes.addAll(way.getNodes()); + } + return nodes; + } + public List getGeometry() { List list = new ArrayList(); route.mergeForwardWays(); @@ -327,7 +379,7 @@ public class TransportRoutePlanner { public List getSegments() { return segments; } - + public double getWalkDist() { double d = finishWalkDist; for (TransportRouteResultSegment s : segments) { @@ -335,7 +387,15 @@ public class TransportRoutePlanner { } return d; } - + + public double getFinishWalkDist() { + return finishWalkDist; + } + + public double getWalkSpeed() { + return cfg.walkSpeed; + } + public double getRouteTime() { return routeTime; } diff --git a/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java b/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java index 5e3a62c05d..f6379410fa 100644 --- a/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java +++ b/OsmAnd-java/src/main/java/net/osmand/search/SearchUICore.java @@ -6,7 +6,9 @@ import net.osmand.PlatformUtil; import net.osmand.ResultMatcher; import net.osmand.binary.BinaryMapIndexReader; import net.osmand.data.Amenity; +import net.osmand.data.City; import net.osmand.data.LatLon; +import net.osmand.data.MapObject; import net.osmand.data.Street; import net.osmand.osm.MapPoiTypes; import net.osmand.search.core.CustomSearchPoiFilter; @@ -16,6 +18,7 @@ import net.osmand.search.core.SearchCoreFactory; import net.osmand.search.core.SearchCoreFactory.SearchAmenityTypesAPI; import net.osmand.search.core.SearchCoreFactory.SearchBuildingAndIntersectionsByStreetAPI; import net.osmand.search.core.SearchCoreFactory.SearchStreetByCityAPI; +import net.osmand.search.core.SearchExportSettings; import net.osmand.search.core.SearchPhrase; import net.osmand.search.core.SearchPhrase.NameStringMatcher; import net.osmand.search.core.SearchResult; @@ -25,14 +28,18 @@ import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; import org.apache.commons.logging.Log; +import org.json.JSONArray; +import org.json.JSONObject; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; +import java.util.Set; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -312,7 +319,7 @@ public class SearchUICore { SearchAmenityTypesAPI searchAmenityTypesAPI = new SearchAmenityTypesAPI(poiTypes); apis.add(searchAmenityTypesAPI); apis.add(new SearchCoreFactory.SearchAmenityByTypeAPI(poiTypes, searchAmenityTypesAPI)); - apis.add(new SearchCoreFactory.SearchAmenityByNameAPI(searchAmenityTypesAPI)); + apis.add(new SearchCoreFactory.SearchAmenityByNameAPI()); SearchBuildingAndIntersectionsByStreetAPI streetsApi = new SearchCoreFactory.SearchBuildingAndIntersectionsByStreetAPI(); apis.add(streetsApi); @@ -480,7 +487,7 @@ public class SearchUICore { } return; } - searchInBackground(phrase, rm); + searchInternal(phrase, rm); if (!rm.isCancelled()) { SearchResultCollection collection = new SearchResultCollection( phrase); @@ -492,6 +499,9 @@ public class SearchUICore { LOG.info("Finishing search <" + phrase + "> Results=" + rm.getRequestResults().size()); } currentSearchResult = collection; + if (phrase.getSettings().isExportObjects()) { + //rm.createTestJSON(collection); + } rm.searchFinished(phrase); if (onResultsComplete != null) { onResultsComplete.run(); @@ -548,7 +558,7 @@ public class SearchUICore { return radius; } - private void searchInBackground(final SearchPhrase phrase, SearchResultMatcher matcher) { + void searchInternal(final SearchPhrase phrase, SearchResultMatcher matcher) { preparePhrase(phrase); ArrayList lst = new ArrayList<>(apis); Collections.sort(lst, new Comparator() { @@ -600,10 +610,7 @@ public class SearchUICore { } } - - - - public static class SearchResultMatcher implements ResultMatcher{ + public static class SearchResultMatcher implements ResultMatcher{ private final List requestResults = new ArrayList<>(); private final ResultMatcher matcher; private final int request; @@ -612,7 +619,8 @@ public class SearchUICore { private final AtomicInteger requestNumber; int count = 0; private SearchPhrase phrase; - + private List exportedObjects; + private List exportedCities; public SearchResultMatcher(ResultMatcher matcher, SearchPhrase phrase, int request, AtomicInteger requestNumber, int totalLimit) { @@ -714,6 +722,108 @@ public class SearchUICore { boolean cancelled = request != requestNumber.get(); return cancelled || (matcher != null && matcher.isCancelled()); } + + public List getExportedObjects() { + return exportedObjects; + } + + public List getExportedCities() { + return exportedCities; + } + + public void exportObject(MapObject object) { + if (exportedObjects == null) { + exportedObjects = new ArrayList<>(); + } + exportedObjects.add(object); + } + + public void exportCity(City city) { + if (exportedCities == null) { + exportedCities = new ArrayList<>(); + } + exportedCities.add(city); + } + + public JSONObject createTestJSON(SearchResultCollection searchResult) { + JSONObject json = new JSONObject(); + + Set amenities = new HashSet<>(); + Set cities; + Set matchedCities = new HashSet<>(); + Set streetCities = new HashSet<>(); + if (exportedCities != null) { + cities = new HashSet<>(exportedCities); + } else { + cities = new HashSet<>(); + } + Set streets = new HashSet<>(); + + for (MapObject obj : exportedObjects) { + if (obj instanceof Amenity) { + amenities.add((Amenity) obj); + } else if (obj instanceof Street) { + Street street = (Street) obj; + streets.add(street); + if (street.getCity() != null) { + final City city = street.getCity(); + cities.add(city); + streetCities.add(city); + } + } else if (obj instanceof City) { + City city = (City) obj; + cities.add(city); + matchedCities.add(city); + } + } + for (City city : cities) { + List cityStreets = city.getStreets(); + for (Street street : streets) { + if (city.equals(street.getCity()) && !cityStreets.contains(street)) { + cityStreets.add(street); + } + } + } + + SearchExportSettings exportSettings = phrase.getSettings().getExportSettings(); + json.put("settings", phrase.getSettings().toJSON()); + json.put("phrase", phrase.getRawUnknownSearchPhrase()); + if (searchResult.searchResults != null && searchResult.searchResults.size() > 0) { + JSONArray resultsArr = new JSONArray(); + for (SearchResult r : searchResult.searchResults) { + resultsArr.put(r.toString()); + } + json.put("results", resultsArr); + } + if (amenities.size() > 0) { + JSONArray amenitiesArr = new JSONArray(); + for (Amenity amenity : amenities) { + amenitiesArr.put(amenity.toJSON()); + } + json.put("amenities", amenitiesArr); + } + if (cities.size() > 0) { + JSONArray citiesArr = new JSONArray(); + for (City city : cities) { + final JSONObject cityObj = city.toJSON(exportSettings.isExportBuildings()); + if (exportedCities.contains(city)) { + if (!exportSettings.isExportEmptyCities()) { + continue; + } + cityObj.put("init", 1); + } + if (matchedCities.contains(city)) { + cityObj.put("matchCity", 1); + } + if (streetCities.contains(city)) { + cityObj.put("matchStreet", 1); + } + citiesArr.put(cityObj); + } + json.put("cities", citiesArr); + } + return json; + } } public static class SearchResultComparator implements Comparator { @@ -731,8 +841,14 @@ public class SearchUICore { @Override public int compare(SearchResult o1, SearchResult o2) { - if (o1.getFoundWordCount() != o2.getFoundWordCount()) { - return -Algorithms.compare(o1.getFoundWordCount(), o2.getFoundWordCount()); + boolean topVisible1 = ObjectType.isTopVisible(o1.objectType); + boolean topVisible2 = ObjectType.isTopVisible(o2.objectType); + if ((!topVisible1 && !topVisible2) || (topVisible1 && topVisible2)) { + if (o1.isUnknownPhraseMatches() != o2.isUnknownPhraseMatches()) { + return o1.isUnknownPhraseMatches() ? -1 : 1; + } else if (o1.getFoundWordCount() != o2.getFoundWordCount()) { + return -Algorithms.compare(o1.getFoundWordCount(), o2.getFoundWordCount()); + } } if (!sortByName) { double s1 = o1.getSearchDistance(loc); @@ -747,6 +863,17 @@ public class SearchUICore { if (st1 != st2) { return Algorithms.compare(st1, st2); } + if (o1.parentSearchResult != null && o2.parentSearchResult != null) { + if (o1.parentSearchResult == o2.parentSearchResult) { + int cmp = collator.compare(o1.localeName, o2.localeName); + if (cmp != 0) { + return cmp; + } + } + double s1 = o1.getSearchDistance(loc, 1); + double s2 = o2.getSearchDistance(loc, 1); + return Double.compare(s1, s2); + } int cmp = collator.compare(o1.localeName, o2.localeName); if (cmp != 0) { return cmp; diff --git a/OsmAnd-java/src/main/java/net/osmand/search/core/ObjectType.java b/OsmAnd-java/src/main/java/net/osmand/search/core/ObjectType.java index 564c46d61a..399fa4011e 100644 --- a/OsmAnd-java/src/main/java/net/osmand/search/core/ObjectType.java +++ b/OsmAnd-java/src/main/java/net/osmand/search/core/ObjectType.java @@ -34,6 +34,10 @@ public enum ObjectType { return t == CITY || t == VILLAGE || t == POSTCODE || t == STREET || t == HOUSE || t == STREET_INTERSECTION; } + public static boolean isTopVisible(ObjectType t) { + return t == POI_TYPE || t == FAVORITE || t == FAVORITE_GROUP || t == WPT || t == LOCATION || t == PARTIAL_LOCATION; + } + public static ObjectType getExclusiveSearchType(ObjectType t) { if (t == FAVORITE_GROUP) { return FAVORITE; diff --git a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java index ae8d81cd68..035f106989 100644 --- a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java +++ b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchCoreFactory.java @@ -226,8 +226,6 @@ public class SearchCoreFactory { return retName; } - - public static class SearchAddressByNameAPI extends SearchBaseAPI { private static final int DEFAULT_ADDRESS_BBOX_RADIUS = 100 * 1000; @@ -315,18 +313,17 @@ public class SearchCoreFactory { if (phrase.isNoSelectedType() && bbox != null && (phrase.isUnknownSearchWordPresent() || phrase.isEmptyQueryAllowed()) && phrase.isSearchTypeAllowed(ObjectType.CITY)) { - String wrd = phrase.getUnknownWordToSearch(); - NameStringMatcher nm = phrase.getNameStringMatcher(wrd, phrase.isUnknownSearchWordComplete()); - String unknownSearchPhrase = phrase.getUnknownSearchPhrase().trim(); - NameStringMatcher phraseMatcher = null; - if (!Algorithms.isEmpty(unknownSearchPhrase)) { - phraseMatcher = new NameStringMatcher(unknownSearchPhrase, StringMatcherMode.CHECK_EQUALS); - } - // NameStringMatcher nm = phrase.getNameStringMatcher(); + String word = phrase.getUnknownWordToSearch(); + NameStringMatcher nm = phrase.getNameStringMatcher(word, phrase.isUnknownSearchWordComplete()); + NameStringMatcher wordEqualsMatcher = phrase.getNameStringMatcher(word, true); + boolean firstUnknownWordMatches = word.equals(phrase.getUnknownSearchWord()); resArray.clear(); resArray = townCitiesQR.queryInBox(bbox, resArray); int limit = 0; for (City c : resArray) { + if (phrase.getSettings().isExportObjects()) { + resultMatcher.exportCity(c); + } SearchResult res = new SearchResult(phrase); res.object = c; res.file = (BinaryMapIndexReader) c.getReferenceFile(); @@ -341,10 +338,8 @@ public class SearchCoreFactory { if (phrase.isEmptyQueryAllowed() && phrase.isEmpty()) { resultMatcher.publish(res); } else if (nm.matches(res.localeName) || nm.matches(res.otherNames)) { - res.firstUnknownWordMatches = wrd.equals(phrase.getUnknownSearchWord()); - if (phraseMatcher != null) { - res.unknownPhraseMatches = phraseMatcher.matches(res.localeName) || phraseMatcher.matches(res.otherNames); - } + res.firstUnknownWordMatches = firstUnknownWordMatches; + res.unknownPhraseMatches = wordEqualsMatcher.matches(res.localeName); subSearchApiOrPublish(phrase, resultMatcher, res, cityApi); } if (limit++ > LIMIT * phrase.getRadiusLevel()) { @@ -354,7 +349,6 @@ public class SearchCoreFactory { } } - private void searchByName(final SearchPhrase phrase, final SearchResultMatcher resultMatcher) throws IOException { if (phrase.getRadiusLevel() > 1 || phrase.getUnknownSearchWordLength() > 3 || phrase.getUnknownSearchWords().size() > 0) { @@ -368,12 +362,14 @@ public class SearchCoreFactory { final int priority = phrase.isNoSelectedType() ? SEARCH_ADDRESS_BY_NAME_PRIORITY : SEARCH_ADDRESS_BY_NAME_PRIORITY_RADIUS2; final BinaryMapIndexReader[] currentFile = new BinaryMapIndexReader[1]; - - + ResultMatcher rm = new ResultMatcher() { int limit = 0; @Override public boolean publish(MapObject object) { + if (phrase.getSettings().isExportObjects()) { + resultMatcher.exportObject(object); + } if (isCancelled()) { return false; } @@ -415,7 +411,6 @@ public class SearchCoreFactory { || !phrase.isSearchTypeAllowed(ObjectType.CITY)) { return false; } - sr.objectType = ObjectType.CITY; sr.priorityDistance = 0.1; } else if (((City)object).isPostcode()) { @@ -425,7 +420,7 @@ public class SearchCoreFactory { } sr.objectType = ObjectType.POSTCODE; sr.priorityDistance = 0; - } else { + } else { if ((locSpecified && !villagesBbox.contains(x, y, x, y)) || !phrase.isSearchTypeAllowed(ObjectType.VILLAGE)) { return false; @@ -460,8 +455,6 @@ public class SearchCoreFactory { return false; } - - @Override public boolean isCancelled() { return limit > LIMIT * phrase.getRadiusLevel() || @@ -472,11 +465,8 @@ public class SearchCoreFactory { SearchPhraseDataType.ADDRESS); String wordToSearch = phrase.getUnknownWordToSearch(); - String unknownSearchPhrase = phrase.getUnknownSearchPhrase().trim(); - NameStringMatcher phraseMatcher = null; - if (!Algorithms.isEmpty(unknownSearchPhrase)) { - phraseMatcher = new NameStringMatcher(unknownSearchPhrase, StringMatcherMode.CHECK_EQUALS); - } + NameStringMatcher wordEqualsMatcher = phrase.getNameStringMatcher(wordToSearch, true); + boolean firstUnknownWordMatches = wordToSearch.equals(phrase.getUnknownSearchWord()); while (offlineIterator.hasNext() && wordToSearch.length() > 0) { BinaryMapIndexReader r = offlineIterator.next(); currentFile[0] = r; @@ -490,10 +480,8 @@ public class SearchCoreFactory { } r.searchAddressDataByName(req); for (SearchResult res : immediateResults) { - res.firstUnknownWordMatches = wordToSearch.equals(phrase.getUnknownSearchWord()); - if (phraseMatcher != null) { - res.unknownPhraseMatches = phraseMatcher.matches(res.localeName) || phraseMatcher.matches(res.otherNames); - } + res.firstUnknownWordMatches = firstUnknownWordMatches; + res.unknownPhraseMatches = wordEqualsMatcher.matches(res.localeName); if (res.objectType == ObjectType.STREET) { City ct = ((Street) res.object).getCity(); phrase.countUnknownWordsMatch(res, @@ -510,18 +498,14 @@ public class SearchCoreFactory { } } - - public static class SearchAmenityByNameAPI extends SearchBaseAPI { private static final int LIMIT = 10000; private static final int BBOX_RADIUS = 500 * 1000; private static final int BBOX_RADIUS_INSIDE = 10000 * 1000; // to support city search for basemap private static final int FIRST_WORD_MIN_LENGTH = 3; - private SearchAmenityTypesAPI searchAmenityTypesAPI; - public SearchAmenityByNameAPI(SearchAmenityTypesAPI searchAmenityTypesAPI) { + public SearchAmenityByNameAPI() { super(ObjectType.POI); - this.searchAmenityTypesAPI = searchAmenityTypesAPI; } @Override @@ -530,7 +514,7 @@ public class SearchCoreFactory { return false; } if (phrase.isNoSelectedType() && phrase.isUnknownSearchWordPresent() - && phrase.isUnknownSearchWordComplete() && searchAmenityTypesAPI.hasFoundPoiTypes()) { + && phrase.isUnknownSearchWordComplete() && phrase.hasUnknownSearchWordPoiTypes()) { return false; } final BinaryMapIndexReader[] currentFile = new BinaryMapIndexReader[1]; @@ -556,6 +540,9 @@ public class SearchCoreFactory { int limit = 0; @Override public boolean publish(Amenity object) { + if (phrase.getSettings().isExportObjects()) { + resultMatcher.exportObject(object); + } if (limit ++ > LIMIT) { return false; } @@ -587,7 +574,7 @@ public class SearchCoreFactory { } sr.priority = SEARCH_AMENITY_BY_NAME_PRIORITY; if (phraseMatcher != null) { - sr.unknownPhraseMatches = phraseMatcher.matches(sr.localeName) || phraseMatcher.matches(sr.otherNames); + sr.unknownPhraseMatches = phraseMatcher.matches(sr.localeName); } phrase.countUnknownWordsMatch(sr); sr.objectType = ObjectType.POI; @@ -643,8 +630,6 @@ public class SearchCoreFactory { } } - - public static class SearchAmenityTypesAPI extends SearchBaseAPI { private Map translatedNames = new LinkedHashMap<>(); @@ -653,30 +638,12 @@ public class SearchCoreFactory { private List customPoiFilters = new ArrayList<>(); private TIntArrayList customPoiFiltersPriorites = new TIntArrayList(); private MapPoiTypes types; - private List foundPoiTypes = new ArrayList<>(); - private SearchPhrase lastSearchedPhrase; public SearchAmenityTypesAPI(MapPoiTypes types) { super(ObjectType.POI_TYPE); this.types = types; } - public List getFoundPoiTypes() { - return foundPoiTypes; - } - - public boolean hasFoundPoiTypes() { - return foundPoiTypes.size() > 0; - } - - public SearchPhrase getLastSearchedPhrase() { - return lastSearchedPhrase; - } - - public void setLastSearchedPhrase(SearchPhrase lastSearchedPhrase) { - this.lastSearchedPhrase = lastSearchedPhrase; - } - public void clearCustomFilters() { this.customPoiFilters.clear(); this.customPoiFiltersPriorites.clear(); @@ -695,8 +662,13 @@ public class SearchCoreFactory { categories = types.getCategories(false); } List results = new ArrayList(); - NameStringMatcher nm = - new NameStringMatcher(phrase.getUnknownSearchPhrase(), StringMatcherMode.CHECK_ONLY_STARTS_WITH_TRIM); + NameStringMatcher nm; + String unknownSearchPhrase = phrase.getUnknownSearchPhrase(); + if (phrase.getUnknownSearchWord().length() < unknownSearchPhrase.length()) { + nm = new NameStringMatcher(unknownSearchPhrase, StringMatcherMode.CHECK_ONLY_STARTS_WITH_TRIM); + } else { + nm = new NameStringMatcher(unknownSearchPhrase, StringMatcherMode.CHECK_STARTS_FROM_SPACE); + } for (AbstractPoiType pf : topVisibleFilters) { if (!phrase.isUnknownSearchWordPresent() || nm.matches(pf.getTranslation()) @@ -728,21 +700,23 @@ public class SearchCoreFactory { List additionals = pt.getPoiAdditionals(); if (additionals != null) { for (PoiType a : additionals) { - if (!a.isReference() && !results.contains(a) - && (nm.matches(a.getEnTranslation()) - || nm.matches(a.getTranslation()) - || nm.matches(a.getSynonyms()))) { - results.add(a); + if (!a.isReference() && !results.contains(a)) { + String enTranslation = a.getEnTranslation().toLowerCase(); + if (!"yes".equals(enTranslation) && !"no".equals(enTranslation) + && (nm.matches(enTranslation) || nm.matches(a.getTranslation()) || nm.matches(a.getSynonyms()))) { + results.add(a); + } } } } } } } - foundPoiTypes = new ArrayList<>(results); - lastSearchedPhrase = phrase; + phrase.setUnknownSearchWordPoiTypes(new ArrayList<>(results)); if (resultMatcher != null) { + String word = phrase.getUnknownSearchWord(); + NameStringMatcher startMatch = new NameStringMatcher(word, StringMatcherMode.CHECK_ONLY_STARTS_WITH); for (AbstractPoiType pt : results) { SearchResult res = new SearchResult(phrase); res.localeName = pt.getTranslation(); @@ -750,6 +724,7 @@ public class SearchCoreFactory { res.priority = SEARCH_AMENITY_TYPE_PRIORITY; res.priorityDistance = 0; res.objectType = ObjectType.POI_TYPE; + res.firstUnknownWordMatches = startMatch.matches(res.localeName); resultMatcher.publish(res); } for (int i = 0; i < customPoiFilters.size(); i++) { @@ -781,12 +756,14 @@ public class SearchCoreFactory { if (!p.isNoSelectedType() && !p.isUnknownSearchWordPresent()) { return -1; } + SearchWord lastSelectedWord = p.getLastSelectedWord(); + if (lastSelectedWord != null && ObjectType.isAddress(lastSelectedWord.getType())) { + return -1; + } return SEARCH_AMENITY_TYPE_API_PRIORITY; } } - - public static class SearchAmenityByTypeAPI extends SearchBaseAPI { private static final int BBOX_RADIUS = 10000; private SearchAmenityTypesAPI searchAmenityTypesAPI; @@ -852,18 +829,16 @@ public class SearchCoreFactory { } searchPoi(phrase, resultMatcher, obj, null, ptf); } else if (searchAmenityTypesAPI != null) { - if (searchAmenityTypesAPI.lastSearchedPhrase == null - || !searchAmenityTypesAPI.lastSearchedPhrase.getUnknownSearchPhrase().equals(phrase.getUnknownSearchPhrase())) { + if (phrase.getUnknownSearchWordPoiTypes() == null) { searchAmenityTypesAPI.search(phrase, null); } - List poiTypes = searchAmenityTypesAPI.getFoundPoiTypes(); - for (AbstractPoiType pt : poiTypes) { - SearchPoiTypeFilter ptf = getPoiTypeFilter(pt); - String customName = phrase.getPoiNameFilter(pt); + AbstractPoiType poiType = phrase.getUnknownSearchWordPoiType(); + if (poiType != null) { + SearchPoiTypeFilter ptf = getPoiTypeFilter(poiType); + String customName = phrase.getPoiNameFilter(poiType); if (customName != null) { - phrase.setUnknownSearchWordPoiType(pt); - searchPoi(phrase, resultMatcher, null, customName, ptf); - break; + phrase.setUnknownSearchWordPoiType(poiType); + searchPoi(phrase, resultMatcher, null, customName.length() == 0 ? null : customName, ptf); } } } @@ -909,6 +884,9 @@ public class SearchCoreFactory { @Override public boolean publish(Amenity object) { + if (phrase.getSettings().isExportObjects()) { + resultMatcher.exportObject(object); + } SearchResult res = new SearchResult(phrase); String poiID = object.getType().getKeyName() + "_" + object.getId(); if(!searchedPois.add(poiID)) { @@ -939,7 +917,14 @@ public class SearchCoreFactory { res.priority = SEARCH_AMENITY_BY_TYPE_PRIORITY; res.priorityDistance = 1; if (phraseMatcher != null) { - res.unknownPhraseMatches = phraseMatcher.matches(res.localeName) || phraseMatcher.matches(res.otherNames); + boolean unknownPhraseMatches = phraseMatcher.matches(res.localeName); + AbstractPoiType unknownSearchWordPoiType = phrase.getUnknownSearchWordPoiType(); + if (unknownPhraseMatches && unknownSearchWordPoiType != null) { + unknownPhraseMatches = !phraseMatcher.matches(unknownSearchWordPoiType.getTranslation()) + && !phraseMatcher.matches(unknownSearchWordPoiType.getEnTranslation()) + && !phraseMatcher.matches(unknownSearchWordPoiType.getSynonyms()); + } + res.unknownPhraseMatches = unknownPhraseMatches; } res.objectType = ObjectType.POI; resultMatcher.publish(res); @@ -988,15 +973,13 @@ public class SearchCoreFactory { @Override public int getSearchPriority(SearchPhrase p) { if ((p.isLastWord(ObjectType.POI_TYPE) && p.getLastTokenLocation() != null) - || (p.isNoSelectedType() && p.isUnknownSearchWordComplete())) { + || (p.isNoSelectedType())) { return SEARCH_AMENITY_BY_TYPE_PRIORITY; } return -1; } } - - public static class SearchStreetByCityAPI extends SearchBaseAPI { private static final int DEFAULT_ADDRESS_BBOX_RADIUS = 100 * 1000; @@ -1058,7 +1041,7 @@ public class SearchCoreFactory { phrase.getNameStringMatcher().matches(res.localeName) || phrase.getNameStringMatcher().matches(res.otherNames); if (phraseMatcher != null) { - res.unknownPhraseMatches = phraseMatcher.matches(res.localeName) || phraseMatcher.matches(res.otherNames); + res.unknownPhraseMatches = phraseMatcher.matches(res.localeName); } res.localeRelatedObjectName = c.getName(phrase.getSettings().getLang(), phrase.getSettings().isTransliterate()); res.object = object; @@ -1094,8 +1077,6 @@ public class SearchCoreFactory { p.isLastWord(ObjectType.VILLAGE); } - - public static class SearchBuildingAndIntersectionsByStreetAPI extends SearchBaseAPI { Street cacheBuilding; @@ -1169,6 +1150,7 @@ public class SearchCoreFactory { } String lw = phrase.getUnknownWordToSearchBuilding(); NameStringMatcher buildingMatch = phrase.getNameStringMatcher(lw, phrase.isLastUnknownSearchWordComplete()); + NameStringMatcher startMatch = new NameStringMatcher(lw, StringMatcherMode.CHECK_ONLY_STARTS_WITH); for (Building b : s.getBuildings()) { SearchResult res = new SearchResult(phrase); boolean interpolation = b.belongsToInterpolation(lw); @@ -1182,6 +1164,7 @@ public class SearchCoreFactory { res.file = file; res.priority = priority; res.priorityDistance = 0; + res.firstUnknownWordMatches = startMatch.matches(res.localeName); res.relatedObject = s; res.localeRelatedObjectName = s.getName(phrase.getSettings().getLang(), phrase.getSettings().isTransliterate()); res.objectType = ObjectType.HOUSE; @@ -1235,8 +1218,6 @@ public class SearchCoreFactory { } } - - public static class SearchLocationAndUrlAPI extends SearchBaseAPI { public SearchLocationAndUrlAPI() { @@ -1355,9 +1336,10 @@ public class SearchCoreFactory { @Override public int getSearchPriority(SearchPhrase p) { + if (!p.isNoSelectedType() || !p.isUnknownSearchWordPresent()) { + return -1; + } return SEARCH_LOCATION_PRIORITY; } } - - } diff --git a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchExportSettings.java b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchExportSettings.java new file mode 100644 index 0000000000..2519d12edc --- /dev/null +++ b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchExportSettings.java @@ -0,0 +1,32 @@ +package net.osmand.search.core; + +public class SearchExportSettings { + private boolean exportEmptyCities; + private boolean exportBuildings; + + public SearchExportSettings() { + exportEmptyCities = true; + exportBuildings = true; + } + + public SearchExportSettings(boolean exportEmptyCities, boolean exportBuildings) { + this.exportEmptyCities = exportEmptyCities; + this.exportBuildings = exportBuildings; + } + + public boolean isExportEmptyCities() { + return exportEmptyCities; + } + + public void setExportEmptyCities(boolean exportEmptyCities) { + this.exportEmptyCities = exportEmptyCities; + } + + public boolean isExportBuildings() { + return exportBuildings; + } + + public void setExportBuildings(boolean exportBuildings) { + this.exportBuildings = exportBuildings; + } +} diff --git a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchPhrase.java b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchPhrase.java index 884ea96b7c..dc1a9d7b75 100644 --- a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchPhrase.java +++ b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchPhrase.java @@ -36,6 +36,7 @@ public class SearchPhrase { private String rawUnknownSearchPhrase = ""; private String unknownSearchPhrase = ""; private AbstractPoiType unknownSearchWordPoiType; + private List unknownSearchWordPoiTypes = null; private NameStringMatcher sm; private SearchSettings settings; @@ -47,7 +48,8 @@ public class SearchPhrase { private static final String ALLDELIMITERS = "\\s|,"; private static final Pattern reg = Pattern.compile(ALLDELIMITERS); private Collator clt; - + private static Comparator commonWordsComparator; + private static Set conjunctions = new TreeSet<>(); static { // the @@ -110,6 +112,20 @@ public class SearchPhrase { conjunctions.add("den"); conjunctions.add("dr"); conjunctions.add("y"); + + commonWordsComparator = new Comparator() { + + @Override + public int compare(String o1, String o2) { + int i1 = CommonWords.getCommonSearch(o1.toLowerCase()); + int i2 = CommonWords.getCommonSearch(o2.toLowerCase()); + if (i1 != i2) { + return icompare(i1, i2); + } + // compare length without numbers to not include house numbers + return -icompare(lengthWithoutNumbers(o1), lengthWithoutNumbers(o2)); + } + }; } @@ -141,7 +157,7 @@ public class SearchPhrase { for(SearchWord w : leftWords) { if(restText.startsWith(w.getWord() + DELIMITER)) { sp.words.add(w); - restText = restText.substring(w.getWord().length() + DELIMITER.length()).trim(); + restText = restText.substring(w.getWord().length() + DELIMITER.length()); } else { break; } @@ -186,7 +202,7 @@ public class SearchPhrase { public boolean isUnknownSearchWordComplete() { - return lastUnknownSearchWordComplete || unknownWords.size() > 0; + return lastUnknownSearchWordComplete || unknownWords.size() > 0 || unknownSearchWordPoiType != null; } public boolean isLastUnknownSearchWordComplete() { @@ -248,6 +264,24 @@ public class SearchPhrase { return getPoiNameFilter(unknownSearchWordPoiType); } + public boolean hasUnknownSearchWordPoiTypes() { + return unknownSearchWordPoiTypes != null && unknownSearchWordPoiTypes.size() > 0; + } + + public List getUnknownSearchWordPoiTypes() { + return unknownSearchWordPoiTypes; + } + + public void setUnknownSearchWordPoiTypes(List unknownSearchWordPoiTypes) { + this.unknownSearchWordPoiTypes = unknownSearchWordPoiTypes; + for (AbstractPoiType pt : unknownSearchWordPoiTypes) { + if (getPoiNameFilter(pt) != null) { + setUnknownSearchWordPoiType(pt); + break; + } + } + } + public String getPoiNameFilter(AbstractPoiType pt) { String nameFilter = null; if (pt != null) { @@ -256,11 +290,11 @@ public class SearchPhrase { String enTranslation = pt.getEnTranslation(); String translation = pt.getTranslation(); String synonyms = pt.getSynonyms(); - if (unknownSearchPhrase.length() > enTranslation.length() && nm.matches(enTranslation)) { + if (unknownSearchPhrase.length() >= enTranslation.length() && nm.matches(enTranslation)) { nameFilter = unknownSearchPhrase.substring(enTranslation.length()).trim(); - } else if (unknownSearchPhrase.length() > translation.length() && nm.matches(translation)) { + } else if (unknownSearchPhrase.length() >= translation.length() && nm.matches(translation)) { nameFilter = unknownSearchPhrase.substring(translation.length()).trim(); - } else if (unknownSearchPhrase.length() > synonyms.length() && nm.matches(synonyms)) { + } else if (unknownSearchPhrase.length() >= synonyms.length() && nm.matches(synonyms)) { nameFilter = unknownSearchPhrase.substring(synonyms.length()).trim(); } } @@ -727,42 +761,32 @@ public class SearchPhrase { } return getUnknownSearchWord(); } - + + private static int lengthWithoutNumbers(String s) { + int len = 0; + for(int k = 0; k < s.length(); k++) { + if (s.charAt(k) >= '0' && s.charAt(k) <= '9') { + + } else { + len++; + } + } + return len; + } + public String getUnknownWordToSearch() { List unknownSearchWords = getUnknownSearchWords(); - String wordToSearch = getUnknownSearchWord(); if (unknownSearchWords.size() > 0) { List searchWords = new ArrayList<>(unknownSearchWords); searchWords.add(0, getUnknownSearchWord()); - Collections.sort(searchWords, new Comparator() { - - private int lengthWithoutNumbers(String s) { - int len = 0; - for(int k = 0; k < s.length(); k++) { - if(s.charAt(k) >= '0' && s.charAt(k) <= '9') { - - } else { - len++; - } - } - return len; + Collections.sort(searchWords, commonWordsComparator); + for (String s : searchWords) { + if (s.length() > 0 && !Character.isDigit(s.charAt(0))) { + return s; } - - @Override - public int compare(String o1, String o2) { - int i1 = CommonWords.getCommonSearch(o1.toLowerCase()); - int i2 = CommonWords.getCommonSearch(o2.toLowerCase()); - if (i1 != i2) { - return icompare(i1, i2); - } - // compare length without numbers to not include house numbers - return -icompare(lengthWithoutNumbers(o1), lengthWithoutNumbers(o2)); - } - }); - wordToSearch = searchWords.get(0); + } } - return wordToSearch; } diff --git a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchResult.java b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchResult.java index 03294e6e8a..7ae713aee7 100644 --- a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchResult.java +++ b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchResult.java @@ -1,11 +1,19 @@ package net.osmand.search.core; -import java.util.Collection; - import net.osmand.binary.BinaryMapIndexReader; +import net.osmand.data.Amenity; +import net.osmand.data.City; import net.osmand.data.LatLon; +import net.osmand.data.Street; +import net.osmand.osm.AbstractPoiType; +import net.osmand.osm.PoiCategory; +import net.osmand.osm.PoiFilter; +import net.osmand.osm.PoiType; +import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; +import java.util.Collection; + public class SearchResult { // search phrase that makes search result valid public SearchPhrase requiredSearchPhrase; @@ -21,24 +29,28 @@ public class SearchResult { public Collection otherWordsMatch = null; public boolean firstUnknownWordMatches = true; public boolean unknownPhraseMatches = false; - - + + public boolean isUnknownPhraseMatches() { + boolean res = unknownPhraseMatches; + if (!res && parentSearchResult != null) { + res = parentSearchResult.unknownPhraseMatches; + } + return res; + } + public SearchResult(SearchPhrase sp) { this.requiredSearchPhrase = sp; } public int getFoundWordCount() { int inc = 0; - if(firstUnknownWordMatches) { + if (firstUnknownWordMatches) { inc = 1; } - if (unknownPhraseMatches) { - inc += 1000; - } - if(otherWordsMatch != null) { + if (otherWordsMatch != null) { inc += otherWordsMatch.size(); } - if(parentSearchResult != null) { + if (parentSearchResult != null) { inc += parentSearchResult.getFoundWordCount(); } return inc; @@ -70,11 +82,64 @@ public class SearchResult { public String localeRelatedObjectName; public Object relatedObject; public double distRelatedObjectName; - - - - - - + @Override + public String toString() { + StringBuilder b = new StringBuilder(); + if (!Algorithms.isEmpty(localeName)) { + b.append(localeName); + } + if (!Algorithms.isEmpty(localeRelatedObjectName)) { + if (b.length() > 0) { + b.append(", "); + } + b.append(localeRelatedObjectName); + if (relatedObject instanceof Street) { + Street street = (Street) relatedObject; + City city = street.getCity(); + if (city != null) { + b.append(", ").append(city.getName(requiredSearchPhrase.getSettings().getLang(), + requiredSearchPhrase.getSettings().isTransliterate())); + } + } + } else if (object instanceof AbstractPoiType) { + if (b.length() > 0) { + b.append(" "); + } + AbstractPoiType poiType = (AbstractPoiType) object; + if (poiType instanceof PoiCategory) { + b.append("(Category)"); + } else if (poiType instanceof PoiFilter) { + b.append("(Filter)"); + } else if (poiType instanceof PoiType) { + PoiType p = (PoiType) poiType; + final AbstractPoiType parentType = p.getParentType(); + if (parentType != null) { + final String translation = parentType.getTranslation(); + b.append("(").append(translation); + if (parentType instanceof PoiCategory) { + b.append(" / Category)"); + } else if (parentType instanceof PoiFilter) { + b.append(" / Filter)"); + } else if (parentType instanceof PoiType) { + PoiType pp = (PoiType) poiType; + PoiFilter filter = pp.getFilter(); + PoiCategory category = pp.getCategory(); + if (filter != null && !filter.getTranslation().equals(translation)) { + b.append(" / ").append(filter.getTranslation()).append(")"); + } else if (category != null && !category.getTranslation().equals(translation)) { + b.append(" / ").append(category.getTranslation()).append(")"); + } else { + b.append(")"); + } + } + } else if (p.getFilter() != null) { + b.append("(").append(p.getFilter().getTranslation()).append(")"); + } else if (p.getCategory() != null) { + b.append("(").append(p.getCategory().getTranslation()).append(")"); + } + } + } + return b.toString(); + } } diff --git a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchSettings.java b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchSettings.java index 186a75ed30..3779bb801a 100644 --- a/OsmAnd-java/src/main/java/net/osmand/search/core/SearchSettings.java +++ b/OsmAnd-java/src/main/java/net/osmand/search/core/SearchSettings.java @@ -3,9 +3,13 @@ package net.osmand.search.core; import net.osmand.binary.BinaryMapIndexReader; import net.osmand.data.LatLon; +import org.json.JSONArray; +import org.json.JSONObject; + import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Locale; // immutable object public class SearchSettings { @@ -19,30 +23,33 @@ public class SearchSettings { private ObjectType[] searchTypes; private boolean emptyQueryAllowed; private boolean sortByName; + private SearchExportSettings exportSettings; + //private SearchExportSettings exportSettings = new SearchExportSettings(false, false); public SearchSettings(SearchSettings s) { if(s != null) { this.radiusLevel = s.radiusLevel; this.lang = s.lang; + this.transliterateIfMissing = s.transliterateIfMissing; this.totalLimit = s.totalLimit; this.offlineIndexes = s.offlineIndexes; this.originalLocation = s.originalLocation; this.searchTypes = s.searchTypes; this.emptyQueryAllowed = s.emptyQueryAllowed; this.sortByName = s.sortByName; + this.exportSettings = s.exportSettings; } } - public SearchSettings(List offlineIndexes) { + public SearchSettings(List offlineIndexes) { this.offlineIndexes = Collections.unmodifiableList(offlineIndexes); } - - + public List getOfflineIndexes() { return offlineIndexes; } - public void setOfflineIndexes(List offlineIndexes) { + public void setOfflineIndexes(List offlineIndexes) { this.offlineIndexes = Collections.unmodifiableList(offlineIndexes); } @@ -131,6 +138,20 @@ public class SearchSettings { return s; } + public SearchExportSettings getExportSettings() { + return exportSettings; + } + + public SearchSettings setExportSettings(SearchExportSettings exportSettings) { + SearchSettings s = new SearchSettings(this); + this.exportSettings = exportSettings; + return s; + } + + public boolean isExportObjects() { + return exportSettings != null; + } + public boolean hasCustomSearchType(ObjectType type) { if (searchTypes != null) { for (ObjectType t : searchTypes) { @@ -141,4 +162,52 @@ public class SearchSettings { } return false; } + + public JSONObject toJSON() { + JSONObject json = new JSONObject(); + if (originalLocation != null) { + json.put("lat", String.format(Locale.US, "%.5f", originalLocation.getLatitude())); + json.put("lon", String.format(Locale.US, "%.5f", originalLocation.getLongitude())); + } + json.put("radiusLevel", radiusLevel); + json.put("totalLimit", totalLimit); + json.put("lang", lang); + json.put("transliterateIfMissing", transliterateIfMissing); + json.put("emptyQueryAllowed", emptyQueryAllowed); + json.put("sortByName", sortByName); + if (searchTypes != null && searchTypes.length > 0) { + JSONArray searchTypesArr = new JSONArray(); + for (ObjectType type : searchTypes) { + searchTypesArr.put(type.name()); + } + json.put("searchTypes", searchTypes); + } + + return json; + } + + public static SearchSettings parseJSON(JSONObject json) { + SearchSettings s = new SearchSettings(new ArrayList()); + if (json.has("lat") && json.has("lon")) { + s.originalLocation = new LatLon(json.getDouble("lat"), json.getDouble("lon")); + } + s.radiusLevel = json.optInt("radiusLevel", 1); + s.totalLimit = json.optInt("totalLimit", -1); + s.transliterateIfMissing = json.optBoolean("transliterateIfMissing", false); + s.emptyQueryAllowed = json.optBoolean("emptyQueryAllowed", false); + s.sortByName = json.optBoolean("sortByName", false); + if (json.has("lang")) { + s.lang = json.getString("lang"); + } + if (json.has("searchTypes")) { + JSONArray searchTypesArr = json.getJSONArray("searchTypes"); + ObjectType[] searchTypes = new ObjectType[searchTypesArr.length()]; + for (int i = 0; i < searchTypesArr.length(); i++) { + String name = searchTypesArr.getString(i); + searchTypes[i] = ObjectType.valueOf(name); + } + s.searchTypes = searchTypes; + } + return s; + } } diff --git a/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java b/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java index b3bba64e41..dcf6082a7f 100644 --- a/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java +++ b/OsmAnd-java/src/main/java/net/osmand/util/Algorithms.java @@ -2,8 +2,12 @@ package net.osmand.util; import net.osmand.IProgress; import net.osmand.PlatformUtil; +import net.osmand.router.GeneralRouter; +import net.osmand.router.RoutingConfiguration; import org.apache.commons.logging.Log; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; import java.io.BufferedReader; import java.io.ByteArrayInputStream; @@ -27,6 +31,7 @@ import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.Stack; import java.util.zip.GZIPInputStream; @@ -691,4 +696,54 @@ public class Algorithms { return str1.compareTo(str2); } + public static String getFileAsString(File file) { + try { + FileInputStream fin = new FileInputStream(file); + BufferedReader reader = new BufferedReader(new InputStreamReader(fin, "UTF-8")); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + if (sb.length() > 0) { + sb.append("\n"); + } + sb.append(line); + } + reader.close(); + fin.close(); + return sb.toString(); + } catch (Exception e) { + return null; + } + } + + public static Map parseStringsXml(File file) throws IOException, XmlPullParserException { + InputStream is = new FileInputStream(file); + XmlPullParser parser = PlatformUtil.newXMLPullParser(); + Map map = new HashMap<>(); + parser.setInput(is, "UTF-8"); + int tok; + String key = null; + StringBuilder text = null; + while ((tok = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (tok == XmlPullParser.START_TAG) { + text = new StringBuilder(); + String name = parser.getName(); + if ("string".equals(name)) { + key = parser.getAttributeValue("", "name"); + } + } else if (tok == XmlPullParser.TEXT) { + if (text != null) { + text.append(parser.getText()); + } + } else if (tok == XmlPullParser.END_TAG) { + if (key != null) { + map.put(key, text.toString()); + } + key = null; + text = null; + } + } + is.close(); + return map; + } } \ No newline at end of file diff --git a/OsmAnd-java/src/main/java/net/osmand/util/GeoPointParserUtil.java b/OsmAnd-java/src/main/java/net/osmand/util/GeoPointParserUtil.java index 9eea38d32e..f6ac336605 100644 --- a/OsmAnd-java/src/main/java/net/osmand/util/GeoPointParserUtil.java +++ b/OsmAnd-java/src/main/java/net/osmand/util/GeoPointParserUtil.java @@ -526,11 +526,20 @@ public class GeoPointParserUtil { if (params.containsKey("z")) { zmPart = params.get("z"); } - String[] vls = silentSplit(path, ","); + String[] vls = null; + if(path.contains("@")) { + path = path.substring(path.indexOf("@") + 1); + if(path.contains(",")) { + vls = silentSplit(path, ","); + } + } + if(vls == null) { + vls = silentSplit(path, ","); + } if (vls.length >= 2) { - double lat = parseSilentDouble(vls[0]); - double lon = parseSilentDouble(vls[1]); + double lat = parseSilentDouble(vls[0], Double.NaN); + double lon = parseSilentDouble(vls[1], Double.NaN); int zoom = GeoParsedPoint.NO_ZOOM; if (vls.length >= 3 || zmPart.length() > 0) { if (zmPart.length() == 0) { @@ -543,7 +552,9 @@ public class GeoPointParserUtil { } zoom = parseZoom(zmPart); } - return new GeoParsedPoint(lat, lon, zoom); + if(!Double.isNaN(lat) && !Double.isNaN(lon)) { + return new GeoParsedPoint(lat, lon, zoom); + } } return new GeoParsedPoint(URLDecoder.decode(opath)); } @@ -566,13 +577,17 @@ public class GeoPointParserUtil { } private static double parseSilentDouble(String zoom) { + return parseSilentDouble(zoom, 0); + } + + private static double parseSilentDouble(String zoom, double vl) { try { if (zoom != null) { return Double.valueOf(zoom); } } catch (NumberFormatException e) { } - return 0; + return vl; } private static int parseSilentInt(String zoom) { @@ -739,8 +754,9 @@ public class GeoPointParserUtil { @Override public String toString() { - return isGeoPoint() ? "GeoParsedPoint [lat=" + lat + ", lon=" + lon + ", zoom=" + zoom - + ", label=" + label + "]" : "GeoParsedPoint [query=" + query; + return isGeoPoint() ? + String.format("GeoParsedPoint [lat=%.5f, lon=%.5f, zoom=%d, label=%s]", lat, lon, zoom, label) : + String.format("GeoParsedPoint [query=%s]",query); } } } diff --git a/OsmAnd-java/src/test/java/net/osmand/search/SearchCoreUITest.java b/OsmAnd-java/src/test/java/net/osmand/search/SearchCoreUITest.java index f596b44879..a130e8b65c 100644 --- a/OsmAnd-java/src/test/java/net/osmand/search/SearchCoreUITest.java +++ b/OsmAnd-java/src/test/java/net/osmand/search/SearchCoreUITest.java @@ -1,22 +1,133 @@ package net.osmand.search; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - import net.osmand.OsmAndCollator; +import net.osmand.ResultMatcher; +import net.osmand.binary.BinaryMapIndexReader; +import net.osmand.data.Amenity; +import net.osmand.data.Building; +import net.osmand.data.City; import net.osmand.data.LatLon; +import net.osmand.data.MapObject; +import net.osmand.data.Street; +import net.osmand.osm.AbstractPoiType; +import net.osmand.osm.MapPoiTypes; import net.osmand.search.SearchUICore.SearchResultCollection; +import net.osmand.search.SearchUICore.SearchResultMatcher; import net.osmand.search.core.SearchPhrase; import net.osmand.search.core.SearchResult; import net.osmand.search.core.SearchSettings; +import net.osmand.util.Algorithms; import net.osmand.util.MapUtils; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; import org.junit.Assert; import org.junit.Test; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; public class SearchCoreUITest { + private static final String SEARCH_RESOURCES_PATH = "src/test/resources/search/"; + private static Map enPhrases = new HashMap<>(); + private static Map phrases = new HashMap<>(); + + static { + MapPoiTypes.setDefault(new MapPoiTypes("src/test/resources/poi_types.xml")); + MapPoiTypes poiTypes = MapPoiTypes.getDefault(); + + try { + enPhrases = Algorithms.parseStringsXml(new File("src/test/resources/phrases/en/phrases.xml")); + //phrases = Algorithms.parseStringsXml(new File("src/test/resources/phrases/ru/phrases.xml")); + phrases = enPhrases; + } catch (IOException e) { + e.printStackTrace(); + } catch (XmlPullParserException e) { + e.printStackTrace(); + } + + poiTypes.setPoiTranslator(new MapPoiTypes.PoiTranslator() { + + @Override + public String getTranslation(AbstractPoiType type) { + AbstractPoiType baseLangType = type.getBaseLangType(); + if (baseLangType != null) { + return getTranslation(baseLangType) + " (" + type.getLang().toLowerCase() + ")"; + } + return getTranslation(type.getIconKeyName()); + } + + @Override + public String getTranslation(String keyName) { + String val = phrases.get("poi_" + keyName); + if (val != null) { + int ind = val.indexOf(';'); + if (ind > 0) { + return val.substring(0, ind); + } + } + return val; + } + + @Override + public String getSynonyms(AbstractPoiType type) { + AbstractPoiType baseLangType = type.getBaseLangType(); + if (baseLangType != null) { + return getSynonyms(baseLangType); + } + return getSynonyms(type.getIconKeyName()); + } + + + @Override + public String getSynonyms(String keyName) { + String val = phrases.get("poi_" + keyName); + if (val != null) { + int ind = val.indexOf(';'); + if (ind > 0) { + return val.substring(ind + 1); + } + return ""; + } + return null; + } + + @Override + public String getEnTranslation(AbstractPoiType type) { + AbstractPoiType baseLangType = type.getBaseLangType(); + if (baseLangType != null) { + return getEnTranslation(baseLangType) + " (" + type.getLang().toLowerCase() + ")"; + } + return getEnTranslation(type.getIconKeyName()); + } + + @Override + public String getEnTranslation(String keyName) { + if (enPhrases.isEmpty()) { + return Algorithms.capitalizeFirstLetter(keyName.replace('_', ' ')); + } + String val = enPhrases.get("poi_" + keyName); + if (val != null) { + int ind = val.indexOf(';'); + if (ind > 0) { + return val.substring(0, ind); + } + } + return val; + } + }); + } + @Test public void testDuplicates() throws IOException { SearchSettings ss = new SearchSettings((SearchSettings)null); @@ -100,4 +211,220 @@ public class SearchCoreUITest { rs.add(res); return res; } + + @Test + public void testSearchJsons() throws IOException { + final File[] files = new File(SEARCH_RESOURCES_PATH).listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String filename) { + return filename.endsWith(".json"); + } + }); + if (files != null) { + for (File f : files) { + //testSearchImpl(f); + } + } + } + + private void testSearchImpl(File jsonFile) throws IOException, JSONException { + String sourceJsonText = Algorithms.getFileAsString(jsonFile); + Assert.assertNotNull(sourceJsonText); + Assert.assertTrue(sourceJsonText.length() > 0); + + BinaryMapIndexReaderTest reader = new BinaryMapIndexReaderTest(); + JSONObject sourceJson = new JSONObject(sourceJsonText); + String phraseText = sourceJson.getString("phrase"); + JSONObject settingsJson = sourceJson.getJSONObject("settings"); + if (sourceJson.has("amenities")) { + JSONArray amenitiesArr = sourceJson.getJSONArray("amenities"); + List amenities = new ArrayList<>(); + for (int i = 0; i < amenitiesArr.length(); i++) { + JSONObject amenityObj = amenitiesArr.getJSONObject(i); + amenities.add(Amenity.parseJSON(amenityObj)); + } + reader.amenities = amenities; + } + if (sourceJson.has("cities")) { + JSONArray citiesArr = sourceJson.getJSONArray("cities"); + List cities = new ArrayList<>(); + List initCities = new ArrayList<>(); + List matchedCities = new ArrayList<>(); + List streetCities = new ArrayList<>(); + for (int i = 0; i < citiesArr.length(); i++) { + JSONObject cityObj = citiesArr.getJSONObject(i); + final City city = City.parseJSON(cityObj); + cities.add(city); + if (cityObj.has("init")) { + initCities.add(city); + } + if (cityObj.has("matchCity")) { + matchedCities.add(city); + } + if (cityObj.has("matchStreet")) { + streetCities.add(city); + } + } + reader.cities = cities; + reader.initCities = initCities; + reader.matchedCities = matchedCities; + reader.streetCities = streetCities; + } + List results = new ArrayList<>(); + if (sourceJson.has("results")) { + JSONArray resultsArr = sourceJson.getJSONArray("results"); + for (int i = 0; i < resultsArr.length(); i++) { + results.add(resultsArr.getString(i)); + } + } + + SearchSettings s = SearchSettings.parseJSON(settingsJson); + s.setOfflineIndexes(Collections.singletonList(reader)); + + SearchPhrase phrase = new SearchPhrase(s, OsmAndCollator.primaryCollator()); + phrase = phrase.generateNewPhrase(phraseText, s); + + final SearchUICore core = new SearchUICore(MapPoiTypes.getDefault(), "en", false); + core.init(); + + ResultMatcher rm = new ResultMatcher() { + @Override + public boolean publish(SearchResult object) { + return true; + } + + @Override + public boolean isCancelled() { + return false; + } + }; + SearchResultMatcher matcher = new SearchResultMatcher(rm, phrase, 1, new AtomicInteger(1), -1); + core.searchInternal(phrase, matcher); + + SearchResultCollection collection = new SearchResultCollection(phrase); + collection.addSearchResults(matcher.getRequestResults(), true, true); + List searchResults = collection.getCurrentSearchResults(); + int i = 0; + for (SearchResult result : searchResults) { + String expected = results.get(i++); + String present = result.toString(); + //System.out.println(present); + Assert.assertEquals(expected, present); + if (i >= results.size()) { + break; + } + } + } + + private static class BinaryMapIndexReaderTest extends BinaryMapIndexReader { + + List amenities = Collections.emptyList(); + List cities = Collections.emptyList(); + List initCities = Collections.emptyList(); + List matchedCities = Collections.emptyList(); + List streetCities = Collections.emptyList(); + + BinaryMapIndexReaderTest() throws IOException { + super(null, null, false); + } + + @Override + public List searchPoiByName(SearchRequest req) throws IOException { + for (Amenity amenity : amenities) { + req.publish(amenity); + } + return req.getSearchResults(); + } + + @Override + public List searchPoi(SearchRequest req) throws IOException { + for (Amenity amenity : amenities) { + req.publish(amenity); + } + return req.getSearchResults(); + } + + @Override + public List getCities(SearchRequest resultMatcher, int cityType) throws IOException { + for (City city : initCities) { + if (resultMatcher != null) { + resultMatcher.publish(city); + } + } + return initCities; + } + + @Override + public int preloadStreets(City c, SearchRequest resultMatcher) throws IOException { + return 0; + } + + @Override + public void preloadBuildings(Street s, SearchRequest resultMatcher) throws IOException { + // cities must be filled with streets and buildings + } + + @Override + public List searchAddressDataByName(SearchRequest req) throws IOException { + for (City city : streetCities) { + for (Street street : city.getStreets()) { + req.publish(street); + } + } + for (City city : matchedCities) { + req.publish(city); + } + return req.getSearchResults(); + } + + @Override + public String getRegionName() { + return "Test region"; + } + + @Override + public boolean containsPoiData(int left31x, int top31y, int right31x, int bottom31y) { + return true; + } + + @Override + public boolean containsMapData() { + return true; + } + + @Override + public boolean containsPoiData() { + return true; + } + + @Override + public boolean containsRouteData() { + return true; + } + + @Override + public boolean containsRouteData(int left31x, int top31y, int right31x, int bottom31y, int zoom) { + return true; + } + + @Override + public boolean containsAddressData(int left31x, int top31y, int right31x, int bottom31y) { + return true; + } + + @Override + public boolean containsMapData(int tile31x, int tile31y, int zoom) { + return true; + } + + @Override + public boolean containsMapData(int left31x, int top31y, int right31x, int bottom31y, int zoom) { + return true; + } + + @Override + public boolean containsAddressData() { + return true; + } + } } diff --git a/OsmAnd-java/src/test/java/net/osmand/util/GeoPointParserUtilTest.java b/OsmAnd-java/src/test/java/net/osmand/util/GeoPointParserUtilTest.java index 14c76a0f2b..05138ea91e 100644 --- a/OsmAnd-java/src/test/java/net/osmand/util/GeoPointParserUtilTest.java +++ b/OsmAnd-java/src/test/java/net/osmand/util/GeoPointParserUtilTest.java @@ -19,6 +19,19 @@ public class GeoPointParserUtilTest { GeoParsedPoint test = GeoPointParserUtil.parse("geo:0,0?q=86HJV99P%2B29"); Assert.assertEquals(test.getQuery(), "86HJV99P+29"); } + + @Test + public void testGoogleMaps() { + // https://www.google.com/maps?daddr=Bahnhofplatz+3,+7000+Chur@46.853582,9.529903 + GeoParsedPoint actual = GeoPointParserUtil.parse( + "https://www.google.com/maps?daddr=Bahnhofplatz+3,+7000+Chur"); + assertGeoPoint(actual, new GeoParsedPoint("Bahnhofplatz 3, 7000 Chur")); + + actual = GeoPointParserUtil.parse( + "https://www.google.com/maps?daddr=Bahnhofplatz+3,+7000+Chur@46.853582,9.529903"); + System.out.println(actual); + assertGeoPoint(actual, new GeoParsedPoint(46.853582, 9.529903)); + } @Test public void testGeoPoint() { diff --git a/OsmAnd-java/src/test/resources/.gitignore b/OsmAnd-java/src/test/resources/.gitignore index 30b9836a2b..079e13cfb2 100644 --- a/OsmAnd-java/src/test/resources/.gitignore +++ b/OsmAnd-java/src/test/resources/.gitignore @@ -1,3 +1,5 @@ -*.json +/*.json +/osm_live/*.json *.obf *.osm +phrases.xml \ No newline at end of file diff --git a/OsmAnd-java/src/test/resources/search/double_parking.json b/OsmAnd-java/src/test/resources/search/double_parking.json new file mode 100644 index 0000000000..0c23cebe3d --- /dev/null +++ b/OsmAnd-java/src/test/resources/search/double_parking.json @@ -0,0 +1,699 @@ +{ + "settings": { + "lat": "55.28666", + "lon": "52.00556", + "radiusLevel": 1, + "totalLimit": -1, + "lang": "", + "transliterateIfMissing": false, + "emptyQueryAllowed": false, + "sortByName": false + }, + "phrase": "parking", + "results": [ + "Parking (Personal transport)", + "Parking entrance (Personal transport)", + "Parking fee (Charging station / Transportation)", + "Parking fee: no (Charging station / Transportation)", + "Parking fee: yes (Charging station / Transportation)", + "Parking lot (Fire hydrant / Emergency infrastructure)", + "Parking tickets (Vending machine / Store)", + "Parking tickets (Vending machine / Store)", + "Parking time limit (Parking / Personal transport)", + "Bicycle parking (Bicycle transport)", + "Motorcycle parking (Personal transport)", + "Parking", + "Parking", + "Parking", + "Parking" + ], + "amenities": [ + { + "lat": "55.29801", + "lon": "51.99100", + "id": 881010149, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.29481", + "lon": "51.98964", + "id": 803545283, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "parking_surface": "surface" + } + }, + { + "name": "Заинская ГРЭС", + "lat": "55.28492", + "lon": "52.02125", + "id": 481494555, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_yes": "yes", + "parking_surface": "surface" + } + }, + { + "lat": "55.29105", + "lon": "51.99531", + "id": 540237721, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.28683", + "lon": "52.01479", + "id": 888343263, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_unpaved": "unpaved", + "access_permissive": "permissive", + "supervised_yes": "yes" + } + }, + { + "lat": "55.30317", + "lon": "52.00192", + "id": 1161722765, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "parking_surface": "surface", + "operator": "cахарный завод" + } + }, + { + "name": "для техосмотра", + "lat": "55.30439", + "lon": "52.01634", + "id": 484385195, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.28615", + "lon": "52.00702", + "id": 1040771455, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.28582", + "lon": "52.00758", + "id": 535925577, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.30950", + "lon": "51.96954", + "id": 1228158833, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "parking_surface": "surface" + } + }, + { + "lat": "55.29345", + "lon": "51.99385", + "id": 535897911, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.30957", + "lon": "52.00374", + "id": 897019441, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.28602", + "lon": "52.00908", + "id": 268235957, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.28777", + "lon": "51.99608", + "id": 4410403520512, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "surface_unpaved": "unpaved", + "supervised_yes": "yes", + "parking_surface": "surface", + "fee_yes": "yes", + "capacity": "70" + } + }, + { + "lat": "55.34092", + "lon": "52.05172", + "id": 2136388576, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no" + } + }, + { + "lat": "55.28271", + "lon": "52.00248", + "id": 536030271, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.28391", + "lon": "52.00273", + "id": 536035007, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "name": "Служебная", + "lat": "55.28615", + "lon": "52.02350", + "id": 481491939, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "surface_asphalt": "asphalt", + "fee_no": "no", + "access_permissive": "permissive", + "supervised_yes": "yes", + "parking_surface": "surface" + } + }, + { + "lat": "55.30969", + "lon": "51.96917", + "id": 1228158821, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.31022", + "lon": "51.97104", + "id": 1228158819, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "parking_surface": "surface" + } + }, + { + "lat": "55.29275", + "lon": "51.99355", + "id": 892959215, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "name": "для посетителей", + "lat": "55.28688", + "lon": "52.00578", + "id": 11390213698, + "subType": "parking", + "type": "transportation" + }, + { + "lat": "55.30838", + "lon": "51.96767", + "id": 1164434353, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "parking_surface": "surface" + } + }, + { + "name": "Южная", + "lat": "55.27739", + "lon": "52.00544", + "id": 641567205, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "surface_unpaved": "unpaved", + "supervised_yes": "yes", + "parking_surface": "surface", + "fee_yes": "yes" + } + }, + { + "lat": "55.28045", + "lon": "51.99550", + "id": 478137507, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "supervised_yes": "yes", + "parking_surface": "surface", + "fee_yes": "yes" + } + }, + { + "name": "Автостоянка №1", + "lat": "55.29028", + "lon": "51.99934", + "id": 502948005, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "surface_gravel": "gravel", + "supervised_yes": "yes", + "parking_surface": "surface", + "fee_yes": "yes" + } + }, + { + "lat": "55.27839", + "lon": "52.00301", + "id": 1168315587, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "parking_surface": "surface", + "operator": "Заинская ЦРБ" + } + }, + { + "lat": "55.30468", + "lon": "52.01335", + "id": 897014805, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.32509", + "lon": "51.99930", + "id": 1172111487, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "name": "Штрафстоянка (Эвакуатор)", + "lat": "55.30439", + "lon": "52.01346", + "id": 5153530824, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "supervised_yes": "yes", + "parking_surface": "surface", + "fee_yes": "yes", + "phone": "+7 (85558) 7-77-01" + } + }, + { + "name": "Три тополя", + "lat": "55.29666", + "lon": "51.99048", + "id": 642175341, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "surface_unpaved": "unpaved", + "supervised_yes": "yes", + "parking_surface": "surface", + "fee_yes": "yes" + } + }, + { + "lat": "55.29077", + "lon": "52.00812", + "id": 1168356553, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "surface_asphalt": "asphalt", + "supervised_yes": "yes", + "parking_surface": "surface", + "fee_yes": "yes" + } + }, + { + "lat": "55.29429", + "lon": "51.99190", + "id": 893048919, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.29959", + "lon": "52.00516", + "id": 1022216961, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.28355", + "lon": "51.99881", + "id": 943827057, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "parking_surface": "surface" + } + }, + { + "lat": "55.28651", + "lon": "52.00479", + "id": 905757839, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.28928", + "lon": "52.00977", + "id": 190467181, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_gravel": "gravel", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.28674", + "lon": "52.00282", + "id": 1168411743, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "name": "для клиентов", + "lat": "55.29116", + "lon": "51.99394", + "id": 821840897, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "surface_asphalt": "asphalt", + "parking_surface": "surface", + "access_customers": "customers" + } + }, + { + "lat": "55.29068", + "lon": "51.99490", + "id": 540238031, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.28982", + "lon": "51.98911", + "id": 190489189, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.29341", + "lon": "51.99072", + "id": 806562897, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no" + } + }, + { + "lat": "55.28042", + "lon": "52.00155", + "id": 1159734951, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.30493", + "lon": "52.01007", + "id": 1172110357, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.30500", + "lon": "52.01108", + "id": 1172110359, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface", + "capacity": "13" + } + }, + { + "lat": "55.29816", + "lon": "51.99222", + "id": 1160388645, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.29015", + "lon": "51.99366", + "id": 503278061, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.30624", + "lon": "52.00475", + "id": 691494051, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface", + "operator": "ЗЗМК - Тимер" + } + }, + { + "name": "для регистрации в ГИБДД", + "lat": "55.30438", + "lon": "52.01483", + "id": 484385289, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + }, + { + "lat": "55.29258", + "lon": "51.99188", + "id": 892959265, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no" + } + }, + { + "lat": "55.28050", + "lon": "52.00200", + "id": 1159662915, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface", + "capacity": "10" + } + }, + { + "lat": "55.29845", + "lon": "51.99057", + "id": 881010015, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "fee_no": "no", + "surface_asphalt": "asphalt", + "supervised_no": "no", + "parking_surface": "surface" + } + } + ] +} \ No newline at end of file diff --git a/OsmAnd-java/src/test/resources/search/poi_biergarten.json b/OsmAnd-java/src/test/resources/search/poi_biergarten.json new file mode 100644 index 0000000000..362e58f4d7 --- /dev/null +++ b/OsmAnd-java/src/test/resources/search/poi_biergarten.json @@ -0,0 +1,662 @@ +{ + "settings": { + "lat": "51.04933", + "lon": "13.73815", + "radiusLevel": 1, + "totalLimit": -1, + "lang": "", + "transliterateIfMissing": false, + "emptyQueryAllowed": false, + "sortByName": false + }, + "phrase": "Biergarten", + "results": [ + "Biergarten (Food)", + "Biergarten Italienisches Dörfchen", + "Biergarten Narrenhäus'l", + "Biergarten Elbsegler", + "Biergarten Elbsegler", + "Biergarten", + "Biergarten", + "Biergarten", + "Biergarten", + "Biergarten" + ], + "amenities": [ + { + "lat": "51.02489", + "lon": "13.69860", + "id": 244538865, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "surface_fine_gravel": "fine_gravel" + } + }, + { + "lat": "51.09478", + "lon": "13.84153", + "id": 865332598, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "Einkehr" + } + }, + { + "lat": "51.01123", + "lon": "13.67918", + "id": 9712288200, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "website": "http://www.zur-linde-freital.de/", + "operator": "Zur Linde", + "image": "http://commons.wikimedia.org/wiki/File:Hotel_und_Gasthaus_Zur_Linde_Freital-Birkigt.jpg" + } + }, + { + "name": "Altes Wettbüro", + "lat": "51.06472", + "lon": "13.74467", + "id": 11451975032, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Tu-Sa 17:00-22:00", + "additionalInfo": { + "wheelchair_limited": "limited", + "outdoor_seating_yes": "yes", + "outdoor_seating_filter_yes": "yes", + "opening_hours": "Tu-Sa 17:00-22:00", + "website": "http://www.altes-wettbuero.de", + "phone": "+49 351 6588983", + "operator": "Falk Gruß" + } + }, + { + "name": "Carolaschlösschen", + "lat": "51.03341", + "lon": "13.76392", + "id": 3704353318, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Mo-Su 11:00-24:00", + "additionalInfo": { + "wheelchair_limited": "limited", + "opening_hours": "Mo-Su 11:00-24:00", + "facebook": "https://www.facebook.com/carolaschloesschen/" + } + }, + { + "lat": "51.01251", + "lon": "13.69407", + "id": 562115695, + "subType": "biergarten", + "type": "sustenance" + }, + { + "name": "Torwirtschaft", + "lat": "51.04156", + "lon": "13.75139", + "id": 517007905, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_yes": "yes", + "website": "http://www.torwirtschaft-dresden.de", + "facebook": "https://www.facebook.com/Torwirtschaft-der-Biergarten-f%C3%BCr-alle-Dynamofans-168677023169963/" + } + }, + { + "name": "Biergarten Italienisches Dörfchen", + "lat": "51.05429", + "lon": "13.73759", + "id": 486232011, + "subType": "biergarten", + "type": "sustenance" + }, + { + "lat": "51.04261", + "lon": "13.75229", + "id": 98518191, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_yes": "yes" + } + }, + { + "name": "Körnergarten", + "names": { + "prefix": "Biergarten" + }, + "lat": "51.05316", + "lon": "13.81192", + "id": 3704353594, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Mo-Su 11:00-24:00", + "additionalInfo": { + "wheelchair_limited": "limited", + "opening_hours": "Mo-Su 11:00-24:00", + "website": "http://www.koernergarten.de/" + } + }, + { + "lat": "51.06397", + "lon": "13.79748", + "id": 670742479, + "subType": "biergarten", + "type": "sustenance" + }, + { + "name": "Weingut Seifert", + "lat": "51.11134", + "lon": "13.66701", + "id": 789740008, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "note": "not really a 'biergarten' :)" + } + }, + { + "lat": "50.98710", + "lon": "13.65086", + "id": 9037536542, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "Zum Gründl" + } + }, + { + "lat": "51.10538", + "lon": "13.62622", + "id": 9289210778, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "Zum Bürgergarten" + } + }, + { + "lat": "51.09459", + "lon": "13.84170", + "id": 5598697742, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "Einkehr" + } + }, + { + "name": "Trobischhof", + "lat": "51.08649", + "lon": "13.71338", + "id": 409847251, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_limited": "limited" + } + }, + { + "name": "Demnitz Elbegarten", + "lat": "51.05328", + "lon": "13.81175", + "id": 771102016, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_no": "no", + "cuisine_german": "german", + "website": "http://www.elbegarten.de" + } + }, + { + "name": "Augustus Garten am Narrenhäusl", + "lat": "51.05697", + "lon": "13.74166", + "id": 465538291, + "subType": "biergarten", + "type": "sustenance" + }, + { + "lat": "51.01529", + "lon": "13.65107", + "id": 7482890514, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "Burgwartschänke" + } + }, + { + "name": "el Horst", + "lat": "51.04361", + "lon": "13.78979", + "id": 559979958, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Oct-Apr: Mo-Fr 17:00-18:00+, Oct-Apr: Sa,PH 11:30-18:00+, May-Sep: Mo-Fr 15:00-18:00+, May-Sep: Sa,PH 11:30-18:00+", + "additionalInfo": { + "opening_hours": "Oct-Apr: Mo-Fr 17:00-18:00+, Oct-Apr: Sa,PH 11:30-18:00+, May-Sep: Mo-Fr 15:00-18:00+, May-Sep: Sa,PH 11:30-18:00+" + } + }, + { + "lat": "51.07376", + "lon": "13.70135", + "id": 553613482, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "Wirtshaus Lindenschänke" + } + }, + { + "name": "Wachstube", + "lat": "51.04254", + "lon": "13.75222", + "id": 46060263, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "11:00+", + "additionalInfo": { + "wheelchair_no": "no", + "opening_hours": "11:00+", + "website": "https://www.torwirtschaft-dresden.de/wachstube/restaurant.php", + "phone": "+49 351 4466975", + "image": "https://commons.wikimedia.org/wiki/File:Torhaus_N_Grosser_Garten_Dresden-2.jpg", + "wheelchair_description:de": "Zugang ins Gebäude nur über Stufen", + "height": "5.7", + "facebook": "https://www.facebook.com/Wachstube-im-Gro%C3%9Fen-Garten-Dresden-144180698984577/", + "email": "info@wachstube-dresden.de" + } + }, + { + "lat": "51.09050", + "lon": "13.70555", + "id": 6300849046, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "Bäckerei Werner" + } + }, + { + "name": "Biergarten Elbsegler", + "lat": "51.05731", + "lon": "13.73973", + "id": 465318113, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_limited": "limited" + } + }, + { + "name": "Straußenwirtschaft Weingut Pesterwitz", + "lat": "51.02883", + "lon": "13.64144", + "id": 478369183, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "website": "https://www.gut-pesterwitz.de/" + } + }, + { + "name": "Schillergarten", + "lat": "51.05228", + "lon": "13.80908", + "id": 493702544, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Mo-Su 11:00-01:00", + "additionalInfo": { + "wheelchair_yes": "yes", + "opening_hours": "Mo-Su 11:00-01:00" + } + }, + { + "lat": "51.04111", + "lon": "13.80554", + "id": 5837244174, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "Astloch" + } + }, + { + "name": "Spitzwegerich", + "lat": "51.07767", + "lon": "13.70757", + "id": 412610355, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_yes": "yes", + "toilets_wheelchair_no": "no" + } + }, + { + "name": "Biergarten Goldener Anker", + "lat": "51.10399", + "lon": "13.62850", + "id": 143015201, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Tu-Su,PH 11:00-21:00", + "additionalInfo": { + "wheelchair_yes": "yes", + "opening_hours": "Tu-Su,PH 11:00-21:00" + } + }, + { + "name": "Bottoms Up", + "lat": "51.06516", + "lon": "13.75615", + "id": 1394136626, + "subType": "biergarten", + "type": "sustenance" + }, + { + "name": "Biergarten Narrenhäus'l", + "lat": "51.05666", + "lon": "13.74130", + "id": 466763007, + "subType": "biergarten", + "type": "sustenance" + }, + { + "name": "Zum Schießhaus", + "lat": "51.05464", + "lon": "13.72727", + "id": 326416539, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Mo-Su,PH 11:00-23:00", + "additionalInfo": { + "wheelchair_yes": "yes", + "toilets_wheelchair_no": "no", + "opening_hours": "Mo-Su,PH 11:00-23:00" + } + }, + { + "name": "Palais Bistro", + "lat": "51.05182", + "lon": "13.73682", + "id": 501015667, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Apr-Oct: Mo-Su 11:00-24:00, Nov-Mar: Mo-Th 12:00-14:30,17:30-24:00, Nov-Mar: Fr-Su 12:00-24:00", + "additionalInfo": { + "wheelchair_yes": "yes", + "opening_hours": "Apr-Oct: Mo-Su 11:00-24:00, Nov-Mar: Mo-Th 12:00-14:30,17:30-24:00, Nov-Mar: Fr-Su 12:00-24:00" + } + }, + { + "name": "Brauhaus am Waldschlößchen", + "lat": "51.06778", + "lon": "13.77786", + "id": 594989978, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Mo-Su 11:00-01:00", + "additionalInfo": { + "wheelchair_limited": "limited", + "opening_hours": "Mo-Su 11:00-01:00" + } + }, + { + "lat": "51.00222", + "lon": "13.68504", + "id": 2469999180, + "subType": "biergarten", + "type": "sustenance" + }, + { + "name": "Biergarten Elbsegler", + "lat": "51.05738", + "lon": "13.73913", + "id": 466539133, + "subType": "biergarten", + "type": "sustenance" + }, + { + "lat": "51.01010", + "lon": "13.66208", + "id": 7987508388, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_yes": "yes", + "operator": "Zum goldenen Löwen" + } + }, + { + "lat": "50.99476", + "lon": "13.72701", + "id": 6765029580, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "Eutschützer Mühle" + } + }, + { + "lat": "50.99295", + "lon": "13.64362", + "id": 6065174964, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "Alte Schmiede" + } + }, + { + "name": "Louisengarten", + "lat": "51.06681", + "lon": "13.75278", + "id": 68516655, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Apr-Sep Su-Th 16:00+; Apr-Sep Fr,Sa 15:00+ || off \"Bei unfreundlichem Wetter\"", + "additionalInfo": { + "wheelchair_no": "no", + "opening_hours": "Apr-Sep Su-Th 16:00+; Apr-Sep Fr,Sa 15:00+ || off \"Bei unfreundlichem Wetter\"", + "website": "http://www.biergarten-dresden.de", + "note": "Neustädter Winter Hüttn von Oktober-Dezember", + "alt_name": "Neustädter Winter Hüttn" + } + }, + { + "lat": "51.00871", + "lon": "13.65959", + "id": 9237211698, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_yes": "yes", + "operator": "Akropolis" + } + }, + { + "lat": "51.03435", + "lon": "13.76042", + "id": 595971770, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Apr-Oct: Mo-Fr 11:00-19:00; Sa-Su 10:00-19:00; PH 10:00-19:00", + "additionalInfo": { + "wheelchair_yes": "yes", + "opening_hours": "Apr-Oct: Mo-Fr 11:00-19:00; Sa-Su 10:00-19:00; PH 10:00-19:00", + "website": "https://www.grosser-garten-dresden.de/de/gaesteservice/gastronomie-shop/", + "phone": "+49 152 37 00 67 53" + } + }, + { + "name": "Landgut Hofewiese", + "lat": "51.10989", + "lon": "13.83207", + "id": 8455509378, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_yes": "yes" + } + }, + { + "name": "Café & Restaurant Saite", + "lat": "51.07636", + "lon": "13.74825", + "id": 577481323, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Mo-Fr 18:00-24:00, Su 10:00-15:00", + "additionalInfo": { + "wheelchair_limited": "limited", + "opening_hours": "Mo-Fr 18:00-24:00, Su 10:00-15:00" + } + }, + { + "name": "Alt-Dresden", + "lat": "51.04977", + "lon": "13.69881", + "id": 970594717, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_limited": "limited", + "phone": "0351 4135133" + } + }, + { + "lat": "51.08677", + "lon": "13.70081", + "id": 413620001, + "subType": "biergarten", + "type": "sustenance" + }, + { + "lat": "50.98618", + "lon": "13.63208", + "id": 10355747978, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "Hirschbergschenke" + } + }, + { + "name": "Fährgarten Johannstadt", + "lat": "51.06151", + "lon": "13.76679", + "id": 4415013507072, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_yes": "yes", + "outdoor_seating_yes": "yes", + "outdoor_seating_filter_yes": "yes", + "website": "http://www.faehrgarten.de/", + "phone": "+49 351 4596262", + "brewery_additional": "Radeberger", + "email": "info@faehrgarten.de" + } + }, + { + "lat": "51.02343", + "lon": "13.73347", + "id": 4563637454, + "subType": "biergarten", + "type": "sustenance" + }, + { + "name": "Besenwirtschaft Steinrücken", + "lat": "51.11590", + "lon": "13.62457", + "id": 7474665554, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "website": "http://www.besenwirtschaft-steinruecken.de/index.html" + } + }, + { + "lat": "51.05181", + "lon": "13.81396", + "id": 9819148822, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "operator": "WSV \"Am Blauen Wunder\"" + } + }, + { + "name": "Zacke", + "lat": "51.01300", + "lon": "13.63618", + "id": 7498805230, + "subType": "biergarten", + "type": "sustenance" + }, + { + "name": "Klotzscher Sommerwirtschaft", + "lat": "51.11072", + "lon": "13.76879", + "id": 717794449, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Mo-Fr 17:00-23:00; Sa-Su 11:00-23:00; Nov-Mar off", + "additionalInfo": { + "opening_hours": "Mo-Fr 17:00-23:00; Sa-Su 11:00-23:00; Nov-Mar off", + "website": "http://klotzschersommerwirtschaft.de", + "phone": "+49 351 8804570", + "fax": "+49 351 8902050", + "email": "Kontakt@klotzscher-sommerwirtschaft.de", + "capacity": "20" + } + }, + { + "name": "Brauhaus Watzke", + "lat": "51.07744", + "lon": "13.71540", + "id": 397976739, + "subType": "biergarten", + "type": "sustenance", + "additionalInfo": { + "wheelchair_limited": "limited", + "toilets_wheelchair_no": "no" + } + }, + { + "name": "Trachauer Sommergarten", + "lat": "51.09166", + "lon": "13.70289", + "id": 1822089672, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "Mo-Su 17:00-21:00", + "additionalInfo": { + "opening_hours": "Mo-Su 17:00-21:00", + "note": "Auch im Winter geöffnet" + } + }, + { + "lat": "51.06520", + "lon": "13.82406", + "id": 11565282314, + "subType": "biergarten", + "type": "sustenance", + "openingHours": "seasonal", + "additionalInfo": { + "outdoor_seating_yes": "yes", + "outdoor_seating_filter_yes": "yes", + "opening_hours": "seasonal" + } + } + ] +} \ No newline at end of file diff --git a/OsmAnd-java/src/test/resources/search/street_komsomolskaya.json b/OsmAnd-java/src/test/resources/search/street_komsomolskaya.json new file mode 100644 index 0000000000..525aee1b12 --- /dev/null +++ b/OsmAnd-java/src/test/resources/search/street_komsomolskaya.json @@ -0,0 +1,2897 @@ +{ + "settings": { + "lat": "55.28666", + "lon": "52.00558", + "radiusLevel": 1, + "totalLimit": -1, + "lang": "", + "transliterateIfMissing": false, + "emptyQueryAllowed": false, + "sortByName": false + }, + "phrase": "комсомольская", + "results": [ + "Комсомольская улица, Заинск", + "Комсомольская улица, Чукмарлы", + "Комсомольская улица, Русский Акташ", + "Комсомольская улица, Альметьевск", + "Комсомольская набережная, Набережные Челны", + "Комсомольская улица, Новое Надырово", + "Комсомольская улица, Тарловка" + ], + "amenities": [ + { + "name": "Комсомольская средняя общеобразовательная школа", + "lat": "55.58591", + "lon": "52.49345", + "id": 408464669, + "subType": "school", + "type": "education" + }, + { + "name": "ПС Комсомольская", + "lat": "55.34627", + "lon": "49.28010", + "id": 549500069, + "subType": "power_substation", + "type": "man_made" + } + ], + "cities": [ + { + "name": "Тараща", + "enName": "Tarascha", + "names": { + "de": "Taraschtscha", + "ru": "Тараща", + "uk": "Тараща", + "prefix": "місто", + "pl": "Taraszcza" + }, + "lat": "49.55550", + "lon": "30.50234", + "id": 620750304, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Сарманово", + "enName": "Sarmanovo", + "names": { + "tt": "Сарман", + "de": "Sarmanowo", + "ru": "Сарманово", + "ja": "サルマノヴォ" + }, + "lat": "55.25225", + "lon": "52.58872", + "id": 337839105, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Богатые Сабы", + "enName": "Bogatye Saby", + "names": { + "tt": "Байлар Сабасы", + "ru": "Богатые Сабы", + "ja": "ボガートィエ・サブィ" + }, + "lat": "56.01062", + "lon": "50.44677", + "id": 701847474, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Прип’ять", + "enName": "Pripyat", + "names": { + "de": "Prypjat", + "no": "Pripjat", + "be": "Прып'яць", + "fi": "Prypjat", + "ru": "Припять", + "pt": "Pripyat", + "bg": "Припя̀т", + "prefix": "місто", + "lt": "Pripetė", + "hr": "Prėpetė", + "fr": "Pripiat", + "hu": "Pripjaty", + "uk": "Прип’ять", + "sk": "Pripjať", + "sl": "Pripjat", + "id": "Prypiat", + "pam": "Prypiat", + "ca": "Pripyat", + "sr": "Припјат", + "sv": "Pripjat", + "ko": "프리피야티", + "eo": "Pripjat", + "it": "Pripjat", + "es": "Prípiat", + "zh": "普里皮亚特", + "et": "Prõpjat", + "cs": "Pripjať", + "ja": "プリピャチ", + "pl": "Prypeć", + "da": "Prypiat", + "ro": "Pripiat", + "nl": "Prypjat" + }, + "lat": "51.40613", + "lon": "30.05711", + "id": 127835001, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Актаныш", + "enName": "Aktanysh", + "names": { + "tt": "Актаныш", + "ru": "Актаныш", + "ja": "アクタヌィシ" + }, + "lat": "55.72218", + "lon": "54.05773", + "id": 606106631, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.72131", + "lon": "54.05469", + "id": 7710 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Баришівка", + "enName": "Baryshivka", + "names": { + "de": "Baryschiwka", + "ru": "Барышевка", + "uk": "Баришівка", + "prefix": "селище міського типу", + "pl": "Baryszówka" + }, + "lat": "50.36453", + "lon": "31.32567", + "id": 337523450, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Старые Алгаши", + "enName": "Staryye Algashi", + "names": { + "cv": "Кивӗ Улхаш", + "ru": "Старые Алгаши" + }, + "lat": "54.66233", + "lon": "47.73995", + "id": 336525031, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.66400", + "lon": "47.73695", + "id": 11319 + } + ], + "matchStreet": 1 + }, + { + "name": "Чорнобиль", + "enName": "Chornobyl", + "names": { + "nn": "Tsjernobyl", + "de": "Tschornobyl", + "no": "Tsjernobyl", + "be": "Чарнобыль", + "fi": "Tšernobyl", + "ru": "Чернобыль", + "pt": "Chernobyl", + "prefix": "місто", + "lt": "Černobylis", + "hr": "Černobil", + "fr": "Tchernobyl", + "hu": "Csernobil", + "yi": "טשערנאבל", + "oc": "Chornobyl", + "uk": "Чорнобиль", + "sk": "Černobyľ", + "sl": "Černobil", + "id": "Chernobyl", + "ca": "Txernòbil", + "scn": "Chernobyl", + "sr": "Černobilj", + "sv": "Tjernobyl", + "ko": "초르노빌", + "af": "Černobylis", + "ms": "Chernobyl", + "eo": "Ĉernobilo", + "it": "Černobyl", + "es": "Chernóbil", + "zh": "切尔诺贝利", + "et": "Tšornobõl", + "cs": "Černobyl", + "eu": "Txernobyl", + "ar": "تشيرنوبيل", + "vi": "Chernobyl", + "lb": "Tschernobyl", + "ja": "チョルノーブィリ", + "pl": "Czarnobyl", + "da": "Tjernobyl", + "he": "צ'רנוביל", + "ro": "Cernobîl", + "nl": "Tsjernobyl", + "tr": "Çernobil" + }, + "lat": "51.27053", + "lon": "30.21944", + "id": 127834997, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Новое Надырово", + "names": { + "tt": "Яңа Нəдер" + }, + "lat": "54.89366", + "lon": "52.47019", + "id": 1504453249, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.89767", + "lon": "52.46710", + "id": 7941 + } + ], + "matchStreet": 1 + }, + { + "name": "Узин", + "enName": "Uzyn", + "names": { + "ru": "Узин", + "uk": "Узин", + "prefix": "місто", + "eo": "Uzin", + "fr": "Ouzyn", + "pl": "Uzyn" + }, + "lat": "49.82847", + "lon": "30.41973", + "id": 337543666, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Камские Поляны", + "enName": "Kamskiye Polyani", + "names": { + "tt": "Кама Аланы", + "de": "Kamskije Poljany", + "ru": "Камские Поляны", + "ja": "カムスキエ・ポリャヌィ" + }, + "lat": "55.42550", + "lon": "51.41016", + "id": 587614698, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Большие Ключи", + "names": { + "tt": "Большие Ключи" + }, + "lat": "55.98296", + "lon": "48.80483", + "id": 652108458, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.98220", + "lon": "48.79005", + "id": 5779 + } + ], + "matchStreet": 1 + }, + { + "name": "Бабинці", + "enName": "Babyntsi", + "names": { + "ru": "Бабинцы", + "uk": "Бабинці", + "prefix": "селище міського типу", + "pl": "Babyntsi" + }, + "lat": "50.64241", + "lon": "30.02907", + "id": 1668578377, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Славутич", + "enName": "Slavutych", + "names": { + "de": "Slawutytsch", + "ru": "Славутич", + "uk": "Славутич", + "prefix": "місто", + "lt": "Slavutičius", + "pl": "Sławutycz" + }, + "lat": "51.52014", + "lon": "30.75623", + "id": 337507300, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Иннополис", + "enName": "Innopolis", + "names": { + "tt": "Иннополис", + "ru": "Иннополис" + }, + "lat": "55.75226", + "lon": "48.74458", + "id": 2294499518, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Лащ-Таяба", + "names": { + "cv": "Лаш Таяпа", + "ru": "Лащ-Таяба" + }, + "lat": "55.03009", + "lon": "47.99935", + "id": 795358551, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.03201", + "lon": "47.99221", + "id": 10729 + } + ], + "matchStreet": 1 + }, + { + "name": "Вишневе", + "enName": "Vyshneve", + "names": { + "de": "Wyschnewe", + "ru": "Вишнёвое", + "uk": "Вишневе", + "prefix": "місто", + "pl": "Wysznewe", + "hu": "Vyshneve" + }, + "lat": "50.39175", + "lon": "30.36791", + "id": 36507630, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Боярка", + "enName": "Boiarka", + "names": { + "ru": "Боярка", + "uk": "Боярка", + "prefix": "місто", + "pl": "Bojarka", + "hu": "Boiarka" + }, + "lat": "50.33567", + "lon": "30.28476", + "id": 36507632, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Васильево", + "enName": "Vasilyevo", + "names": { + "tt": "Васильево", + "de": "Wassiljewo", + "ru": "Васильево", + "ja": "ヴァシーリエヴォ" + }, + "lat": "55.83729", + "lon": "48.67924", + "id": 336524045, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.83829", + "lon": "48.66132", + "id": 2221 + } + ], + "matchStreet": 1 + }, + { + "name": "Волжск", + "enName": "Volzhsk", + "names": { + "tt": "Волжск", + "de": "Wolschsk", + "ru": "Волжск", + "chm": "Юлсер-Ола", + "eo": "Volĵsk", + "it": "Volžsk", + "fr": "Voljsk", + "cs": "Volžsk", + "cv": "Волжск", + "uk": "Волзьк", + "mhr": "Юлсер-Ола", + "ja": "ヴォルシスク", + "pl": "Wołżsk", + "tr": "Voljsk" + }, + "lat": "55.86366", + "lon": "48.36186", + "id": 336523790, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "names": { + "ru": "Комсомольская улица" + }, + "lat": "55.86639", + "lon": "48.36347", + "id": 1897 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Бориспіль", + "enName": "Boryspil", + "names": { + "de": "Boryspil", + "ru": "Борисполь", + "uk": "Бориспіль", + "prefix": "місто", + "eo": "Borispilo", + "fr": "Boryspil", + "pl": "Boryspol", + "hu": "Boryspil" + }, + "lat": "50.35121", + "lon": "30.95076", + "id": 26150796, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Атабаево", + "names": { + "tt": "Атабай" + }, + "lat": "55.28842", + "lon": "49.35494", + "id": 703108525, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.28848", + "lon": "49.34872", + "id": 2636 + } + ], + "matchStreet": 1 + }, + { + "name": "Клавдієво-Тарасове", + "enName": "Klavdiievo-Tarasove", + "names": { + "ru": "Клавдиево-Тарасово", + "uk": "Клавдієво-Тарасове", + "prefix": "селище міського типу", + "pl": "Klavdiievo-Tarasove" + }, + "lat": "50.58414", + "lon": "30.00950", + "id": 337519388, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Гребінки", + "enName": "Hrebinky", + "names": { + "ru": "Гребёнки", + "uk": "Гребінки", + "prefix": "селище міського типу", + "pl": "Hrebinky" + }, + "lat": "49.95346", + "lon": "30.17635", + "id": 337539356, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Чистополь", + "enName": "Chistopol", + "names": { + "tt": "Чистай", + "de": "Tschistopol", + "cv": "Чистай", + "ru": "Чистополь", + "ja": "チストポリ" + }, + "lat": "55.37148", + "lon": "50.63674", + "id": 295864677, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.37373", + "lon": "50.65916", + "id": 4322 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Ірпінь", + "enName": "Irpin", + "names": { + "de": "Irpin", + "ru": "Ирпень", + "uk": "Ірпінь", + "prefix": "місто", + "fr": "Irpine", + "pl": "Irpień", + "hu": "Irpin" + }, + "lat": "50.51665", + "lon": "30.25004", + "id": 36505064, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Тарловка", + "enName": "Tarlovka", + "lat": "55.75428", + "lon": "52.32179", + "id": 573214945, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.75495", + "lon": "52.32159", + "id": 11794 + } + ], + "matchStreet": 1 + }, + { + "name": "Заинск", + "enName": "Zainsk", + "names": { + "tt": "Зәй", + "de": "Sainsk", + "ru": "Заинск", + "ja": "ザインスク" + }, + "lat": "55.28665", + "lon": "52.00561", + "id": 337839081, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.28014", + "lon": "52.00160", + "id": 729 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Яготин", + "enName": "Yahotyn", + "names": { + "de": "Jahotyn", + "ru": "Яготин", + "uk": "Яготин", + "prefix": "місто", + "pl": "Jagodzin" + }, + "lat": "50.27589", + "lon": "31.76349", + "id": 337525267, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Лесная Хмелёвка", + "lat": "54.52728", + "lon": "49.66655", + "id": 28349988061, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.52387", + "lon": "49.66524", + "id": 9249 + } + ], + "matchStreet": 1 + }, + { + "name": "Немішаєве", + "enName": "Nemishaieve", + "names": { + "ru": "Немешаево", + "uk": "Немішаєве", + "prefix": "селище міського типу", + "fr": "Nemishayeve" + }, + "lat": "50.56801", + "lon": "30.10151", + "id": 337519630, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Кагарлик", + "enName": "Kaharlyk", + "names": { + "de": "Kaharlyk", + "ru": "Кагарлык", + "uk": "Кагарлик", + "prefix": "місто", + "pl": "Kaharłyk" + }, + "lat": "49.86507", + "lon": "30.82268", + "id": 1819209275, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Муслюмово", + "enName": "Muslyumovo", + "names": { + "tt": "Мөслим", + "ru": "Муслюмово", + "ja": "ムスリュモヴォ" + }, + "lat": "55.30593", + "lon": "53.19415", + "id": 337839100, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.30712", + "lon": "53.18859", + "id": 7660 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Менделеевск", + "enName": "Mendeleyevsk", + "names": { + "tt": "Менделеевск", + "de": "Mendelejewsk", + "ru": "Менделеевск", + "ja": "メンデリェーエフスク" + }, + "lat": "55.88787", + "lon": "52.30844", + "id": 321322750, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Верхний Услон", + "enName": "Verhnij Uslon", + "names": { + "tt": "Югары Ослан", + "de": "Werchni Uslonn", + "ru": "Верхний Услон", + "ja": "ヴェルフニー・ウスロン", + "ba": "Үрге Ослан" + }, + "lat": "55.76855", + "lon": "48.98293", + "id": 703197622, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Зеленодольск", + "enName": "Zelenodol'sk", + "names": { + "tt": "Яшел Үзән", + "de": "Selenodolsk", + "ru": "Зеленодольск", + "ko": "젤레노돌스크", + "uk": "Зеленодольськ", + "mhr": "Парат", + "ja": "ゼレノドリスク", + "eo": "Zelenodolsk", + "fa": "زلنودولسک، روسیه", + "pl": "Zielonodolsk", + "ro": "Zelenodolsk", + "sr": "Зеленодољск" + }, + "lat": "55.84776", + "lon": "48.50970", + "id": 336524051, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "names": { + "ru": "Комсомольская улица" + }, + "lat": "55.84713", + "lon": "48.49792", + "id": 1958 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Вятские Поляны", + "enName": "Vyatskiye Polyany", + "names": { + "tt": "Нократ Аланы", + "de": "Wjatskije Poljany", + "ja": "ヴャーツキエ・ポリャーヌィ" + }, + "lat": "56.23101", + "lon": "51.02939", + "id": 3237869, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Глеваха", + "enName": "Hlevakha", + "names": { + "kk": "Glevaha", + "de": "Hlewacha", + "ceb": "Hlevakha", + "be": "Глэваха", + "ru": "Глеваха", + "prefix": "селище міського типу", + "fr": "Hlevakha", + "cs": "Hlevacha", + "ar": "هليفاخا", + "hy": "Հլեվահա", + "uk": "Глеваха", + "pl": "Hłewacha", + "tr": "Glevaha", + "rmy": "Hlevaha" + }, + "lat": "50.25968", + "lon": "30.30591", + "id": 337525824, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Казань", + "enName": "Kazan", + "names": { + "tt": "Казан", + "de": "Kasan", + "hi": "काज़ान", + "fi": "Kazan", + "ru": "Казань", + "pt": "Cazã", + "lt": "Kazanė", + "lv": "Kazaņa", + "fr": "Kazan", + "hu": "Kazany", + "hy": "Կազան", + "oc": "Kazan", + "ka": "ყაზანი", + "uk": "Казань", + "sk": "Kazaň", + "sr": "Казањ", + "kn": "ಕಾಜಾ಼ನ್", + "os": "Хъазан", + "chm": "Озаҥ", + "it": "Kazan'", + "kv": "Казан", + "es": "Kazán", + "zh": "喀山", + "cs": "Kazaň", + "ar": "قازان", + "cv": "Хусан", + "mhr": "Озаҥ", + "ja": "カザン", + "pl": "Kazań", + "da": "Kazan", + "he": "קאזאן", + "ro": "Kazan", + "nl": "Kazan", + "ba": "Ҡаҙан", + "udm": "Кузон" + }, + "lat": "55.78235", + "lon": "49.12423", + "id": 27504067, + "type": "CITY", + "listOfStreets": [ + { + "name": "Комсомольская улица (Козья слобода)", + "lat": "55.81106", + "lon": "49.09485", + "id": 2013 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Володарка", + "enName": "Volodarka", + "names": { + "de": "Wolodarka", + "ru": "Володарка", + "uk": "Володарка", + "prefix": "селище міського типу", + "pl": "Wołodarka" + }, + "lat": "49.52250", + "lon": "29.92805", + "id": 337556285, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Старое Дрожжаное", + "enName": "Staroye Drozhzhanoye", + "names": { + "tt": "Иске Чүпрәле", + "ru": "Старое Дрожжаное", + "ja": "スターロエ・ドロジジャノエ" + }, + "lat": "54.72499", + "lon": "47.56454", + "id": 336525099, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Ворзель", + "enName": "Vorzel", + "names": { + "ru": "Ворзель", + "uk": "Ворзель", + "prefix": "селище міського типу", + "pl": "Vorzel" + }, + "lat": "50.54573", + "lon": "30.15629", + "id": 337519932, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Джалиль", + "names": { + "tt": "Җәлил", + "ja": "ジャリリ" + }, + "lat": "55.02197", + "lon": "52.73899", + "id": 371194680, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Городок", + "names": { + "ru": "Городок", + "uk": "Городок", + "prefix": "селище міського типу", + "pl": "Gródek" + }, + "lat": "50.58336", + "lon": "29.47816", + "id": 6726413, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Біла Церква", + "enName": "Bila Tserkva", + "names": { + "de": "Bila Zerkwa", + "be": "Белая Царква", + "ru": "Белая Церковь", + "prefix": "місто", + "lt": "Bila Cerkva", + "eo": "Bila-Cerkva", + "fr": "Bila Tserkva", + "hu": "Bila Cerkva", + "et": "Bila Tserkva", + "cs": "Bila Cerkva", + "uk": "Біла Церква", + "pl": "Biała Cerkiew", + "ro": "Bila Țerkva", + "sr": "Била Церква" + }, + "lat": "49.80382", + "lon": "30.11170", + "id": 255259998, + "type": "CITY", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Старая Майна", + "enName": "Staraya Mayna", + "names": { + "ru": "Старая Майна", + "ja": "スターラヤ・マイナ" + }, + "lat": "54.61114", + "lon": "48.92349", + "id": 11135157981, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.60671", + "lon": "48.91229", + "id": 8342 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Высокая Гора", + "names": { + "tt": "Биектау", + "ja": "ヴイソーカヤ・ゴラー" + }, + "lat": "55.91018", + "lon": "49.30720", + "id": 704304024, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Агидель", + "enName": "Agidel", + "names": { + "tt": "Агыйдел", + "ru": "Агидель", + "ja": "アギデーリ", + "ba": "Ағиҙел", + "et": "Agidel" + }, + "lat": "55.89928", + "lon": "53.93742", + "id": 251071107, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Большая Атня", + "enName": "Bolshaya Atnya", + "names": { + "tt": "Олы Әтнә", + "ru": "Большая Атня", + "ja": "ボリシャヤ・アトニャ" + }, + "lat": "56.24947", + "lon": "49.45390", + "id": 698459097, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "56.24843", + "lon": "49.45315", + "id": 7008 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Болгар", + "enName": "Bolgar", + "names": { + "tt": "Болгар", + "cv": "Пӑлхар", + "ru": "Болгар", + "ja": "ボルガル" + }, + "lat": "54.97474", + "lon": "49.03020", + "id": 599924350, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.96961", + "lon": "49.02911", + "id": 7480 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Челно-Вершины", + "lat": "54.41588", + "lon": "51.08582", + "id": 8519359, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Бородянка", + "enName": "Borodianka", + "names": { + "de": "Borodjanka", + "ru": "Бородянка", + "uk": "Бородянка", + "prefix": "селище міського типу", + "pl": "Borodzianka" + }, + "lat": "50.64657", + "lon": "29.92558", + "id": 295809953, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Красятичі", + "enName": "Krasiatychi", + "names": { + "de": "Krasjatytschi", + "ru": "Красятичи", + "uk": "Красятичі", + "prefix": "селище міського типу", + "pl": "Krasiatyczi" + }, + "lat": "51.07829", + "lon": "29.63779", + "id": 337512030, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Іванків", + "enName": "Ivankiv", + "names": { + "de": "Iwankiw", + "ru": "Иванков", + "uk": "Іванків", + "prefix": "селище міського типу", + "pl": "Iwanków" + }, + "lat": "50.93300", + "lon": "29.90463", + "id": 337513819, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Русский Акташ", + "enName": "Russkiy Aktash", + "names": { + "tt": "Рус Акташы", + "ru": "Русский Акташ", + "ja": "ルースキー・アクタシ" + }, + "lat": "55.04037", + "lon": "52.11699", + "id": 337839143, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.04117", + "lon": "52.11530", + "id": 148 + } + ], + "matchStreet": 1 + }, + { + "name": "Данилкино", + "lat": "56.07810", + "lon": "48.79013", + "id": 661092578, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "56.07952", + "lon": "48.79226", + "id": 2271 + } + ], + "matchStreet": 1 + }, + { + "name": "Ставище", + "enName": "Stavysche", + "names": { + "de": "Stawyschtsche", + "ru": "Ставище", + "uk": "Ставище", + "prefix": "селище міського типу", + "pl": "Stawyszcze" + }, + "lat": "49.38620", + "lon": "30.18316", + "id": 337560920, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Новошешминск", + "enName": "Novosheshminsk", + "names": { + "tt": "Яңа Чишмә", + "de": "Nowoscheschminsk", + "ru": "Новошешминск", + "ja": "ノヴォシェシミンスク" + }, + "lat": "55.06165", + "lon": "51.22489", + "id": 337839146, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Нырья", + "names": { + "tt": "Нырья" + }, + "lat": "56.19929", + "lon": "50.58311", + "id": 796778738, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "56.20261", + "lon": "50.58084", + "id": 12056 + } + ], + "matchStreet": 1 + }, + { + "name": "Малтабарово", + "names": { + "tt": "Малтабар" + }, + "lat": "55.79553", + "lon": "52.78860", + "id": 593611502, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Комсомольская", + "lat": "55.79264", + "lon": "52.78630", + "id": 12145 + } + ], + "matchStreet": 1 + }, + { + "name": "Билярск", + "names": { + "tt": "Биләр" + }, + "lat": "54.98370", + "lon": "50.38888", + "id": 336524609, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.98580", + "lon": "50.39718", + "id": 199 + } + ], + "matchStreet": 1 + }, + { + "name": "Кузкеево", + "names": { + "tt": "Күзкәй" + }, + "lat": "55.77079", + "lon": "52.80849", + "id": 593611503, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.77212", + "lon": "52.79931", + "id": 8524 + } + ], + "matchStreet": 1 + }, + { + "name": "Актюбинский", + "names": { + "tt": "Актүбә", + "ru": "Актюбинский", + "ja": "アクチュビンスキー" + }, + "lat": "54.81319", + "lon": "52.80257", + "id": 445771973, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.81157", + "lon": "52.81645", + "id": 4525 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Яльчики", + "names": { + "de": "Jaltschiki", + "cv": "Елчӗк", + "ru": "Яльчики", + "ja": "ヤリチキ" + }, + "lat": "55.16287", + "lon": "48.00334", + "id": 850408359, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Азнакаево", + "enName": "Aznakayevo", + "names": { + "tt": "Азнакай", + "de": "Asnakajewo", + "cv": "Аснакай", + "ru": "Азнакаево", + "ja": "アズナカエヴォ" + }, + "lat": "54.86296", + "lon": "53.07922", + "id": 337839164, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Карабаш", + "enName": "Karabash", + "names": { + "tt": "Карабаш", + "ja": "カラバシ" + }, + "lat": "54.69440", + "lon": "52.58739", + "id": 2406531050, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.69694", + "lon": "52.58593", + "id": 7913 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Переяслав-Хмельницький", + "enName": "Pereiaslav-Khmelnytskyi", + "names": { + "de": "Perejaslaw-Chmelnyzkyj", + "be": "Пераяслаў-Хмяльніцкі", + "ru": "Переяслав-Хмельницкий", + "uk": "Переяслав-Хмельницький", + "prefix": "місто", + "eo": "Perejaslav-Ĥmelnickij", + "pl": "Perejasław Chmielnicki" + }, + "lat": "50.06440", + "lon": "31.44474", + "id": 337535557, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Нижнекамск", + "enName": "Nizhnekamsk", + "names": { + "cs": "Nižněkamsk", + "tt": "Түбән Кама", + "de": "Nischnekamsk", + "ru": "Нижнекамск", + "uk": "Нижньокамськ", + "ja": "ニジネカムスク", + "lt": "Nižnekamskas", + "fr": "Nijnekamsk", + "pl": "Niżniekamsk", + "es": "Nizhnekamsk" + }, + "lat": "55.64129", + "lon": "51.81603", + "id": 553037933, + "type": "CITY", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Малая Пурга", + "enName": "Malaya Purga", + "names": { + "ja": "マラヤ・プルガ", + "udm": "Пичи Пурга" + }, + "lat": "56.54955", + "lon": "53.00551", + "id": 621090917, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Шемурша", + "names": { + "de": "Schemurscha", + "cv": "Шӑмӑршӑ", + "ja": "シェムルシャー" + }, + "lat": "54.88548", + "lon": "47.51651", + "id": 855192927, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Агрыз", + "enName": "Agryz", + "names": { + "tt": "Әгерҗе", + "ru": "Агрыз", + "ja": "アグルイズ" + }, + "lat": "56.52546", + "lon": "52.99725", + "id": 1773881620, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "56.53128", + "lon": "53.00704", + "id": 7971 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Лениногорск", + "enName": "Leninogorsk", + "names": { + "tt": "Лениногорск", + "de": "Leninogorsk", + "cv": "Ҫӗнӗ Пӗҫмен", + "ru": "Лениногорск", + "ja": "レニノゴルスク" + }, + "lat": "54.59943", + "lon": "52.44691", + "id": 273310116, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.60837", + "lon": "52.46062", + "id": 1038 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Аксубаево", + "enName": "Aksubayevo", + "names": { + "tt": "Аксубай", + "de": "Aksubajewo", + "cv": "Аксу", + "ru": "Аксубаево", + "ja": "アクスバエヴォ" + }, + "lat": "54.84635", + "lon": "50.80554", + "id": 336524899, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.85470", + "lon": "50.81432", + "id": 7569 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Нурлат", + "enName": "Nurlat", + "names": { + "tt": "Нурлат", + "de": "Nurlat", + "cv": "Нурлат", + "ru": "Нурлат", + "ja": "ヌルラート" + }, + "lat": "54.42973", + "lon": "50.80196", + "id": 336525410, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.41543", + "lon": "50.80589", + "id": 1717 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Пісківка", + "enName": "Piskivka", + "names": { + "ru": "Песковка", + "uk": "Пісківка", + "prefix": "селище міського типу", + "pl": "Piskivka" + }, + "lat": "50.69686", + "lon": "29.59311", + "id": 337517677, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Октябрьский", + "enName": "Oktjabrski", + "names": { + "tt": "Октябрьски", + "de": "Oktjabrski", + "ru": "Октябрьский", + "ja": "オクチャブリスキー", + "lt": "Oktiabrskis", + "ba": "Октябрьский", + "et": "Oktjabrski" + }, + "lat": "54.48097", + "lon": "53.46600", + "id": 191901380, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "names": { + "ru": "Комсомольская улица" + }, + "lat": "54.48629", + "lon": "53.44462", + "id": 1564 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Куйбышевский Затон", + "names": { + "tt": "Куйбышев Затоны", + "ja": "クイビシェフスキー・ザトン" + }, + "lat": "55.15645", + "lon": "49.16940", + "id": 697269501, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.16218", + "lon": "49.17156", + "id": 10632 + } + ], + "matchStreet": 1 + }, + { + "name": "Буча", + "enName": "Bucha", + "names": { + "de": "Butscha", + "ru": "Буча", + "prefix": "місто", + "uk": "Буча", + "fr": "Boutcha", + "pl": "Bucza" + }, + "lat": "50.54858", + "lon": "30.22071", + "id": 312987923, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Базарные Матаки", + "enName": "Bazarnyye Mataki", + "names": { + "tt": "Базарлы Матак", + "ja": "バザールヌイエ・マターキ" + }, + "lat": "54.90349", + "lon": "49.92754", + "id": 336524666, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Борова", + "enName": "Borova", + "names": { + "ru": "Боровая", + "uk": "Борова", + "prefix": "селище міського типу", + "pl": "Borowa" + }, + "lat": "50.17834", + "lon": "30.10254", + "id": 337527400, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Нижняя Мактама", + "names": { + "tt": "Түбəн Мактама", + "ja": "ニジニャヤ・マクタマ" + }, + "lat": "54.86247", + "lon": "52.42313", + "id": 1804405492, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Велика Димерка", + "enName": "Velyka Dymerka", + "names": { + "ru": "Великая Дымерка", + "uk": "Велика Димерка", + "prefix": "селище міського типу", + "pl": "Wielka Dymirka" + }, + "lat": "50.59183", + "lon": "30.90109", + "id": 337519261, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Вишгород", + "enName": "Vyshhorod", + "names": { + "de": "Wyschhorod", + "ru": "Вышгород", + "uk": "Вишгород", + "prefix": "місто", + "fr": "Vychhorod", + "pl": "Wyszogród" + }, + "lat": "50.58243", + "lon": "30.48513", + "id": 289308798, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Обухів", + "enName": "Obukhiv", + "names": { + "de": "Obuchiw", + "ru": "Обухов", + "uk": "Обухів", + "prefix": "місто", + "fr": "Oboukhiv", + "pl": "Obuchów", + "hu": "Obukhiv" + }, + "lat": "50.11020", + "lon": "30.62675", + "id": 337534108, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Черемшан", + "enName": "Cheremshan", + "names": { + "tt": "Чирмешән", + "cv": "Ҫарӑмсан", + "ru": "Черемшан", + "ja": "チェレムシャン" + }, + "lat": "54.66110", + "lon": "51.50532", + "id": 337839206, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Українка", + "enName": "Ukrainka", + "names": { + "ru": "Украинка", + "uk": "Українка", + "prefix": "місто", + "pl": "Ukrajinka" + }, + "lat": "50.15372", + "lon": "30.74206", + "id": 337528474, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Калинівка", + "enName": "Kalynivka", + "names": { + "kk": "Kalyni'vka", + "de": "Kalyniwka (Wassylkiw)", + "hy": "Կալյնիվկա", + "ru": "Калиновка", + "vi": "Kalynivka", + "prefix": "селище міського типу", + "uk": "Калинівка", + "ja": "カルィーニウカ", + "fr": "Kalynivka", + "pl": "Kalinówka", + "ro": "Kalînivka, Vasîlkiv", + "tr": "Kalınivka" + }, + "lat": "50.22573", + "lon": "30.22618", + "id": 337526421, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Фастів", + "enName": "Fastiv", + "names": { + "de": "Fastiw", + "ru": "Фастов", + "uk": "Фастів", + "prefix": "місто", + "eo": "Fastiv", + "fr": "Fastiv", + "pl": "Fastów", + "hu": "Fastiv" + }, + "lat": "50.07993", + "lon": "29.91629", + "id": 337535126, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Уруссу", + "enName": "Urussu", + "names": { + "tt": "Урыссу", + "ru": "Уруссу", + "ja": "ウルスー" + }, + "lat": "54.59933", + "lon": "53.46632", + "id": 337839216, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.60163", + "lon": "53.44535", + "id": 2599 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Елабуга", + "enName": "Elabuga", + "names": { + "tt": "Алабуга", + "ar": "ييلابوغا", + "de": "Jelabuga", + "cv": "Алапӳ", + "ru": "Елабуга", + "uk": "Єлабуга", + "ja": "エラブガ", + "tt-lat": "Alabuğa", + "fa": "یلابوگا", + "tr": "Alabuga", + "ba": "Алабуға" + }, + "lat": "55.75771", + "lon": "52.05400", + "id": 153568874, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Ютаза", + "enName": "Yutaza", + "names": { + "tt": "Ютазы", + "ru": "Ютаза" + }, + "lat": "54.58879", + "lon": "53.27073", + "id": 337839220, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.59253", + "lon": "53.26195", + "id": 2683 + } + ], + "matchStreet": 1 + }, + { + "name": "Макарів", + "enName": "Makariv", + "names": { + "de": "Makariw", + "ru": "Макаров", + "uk": "Макарів", + "prefix": "селище міського типу", + "pl": "Makarow" + }, + "lat": "50.46399", + "lon": "29.80690", + "id": 1494035871, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Кукмор", + "enName": "Kukmor", + "names": { + "tt": "Кукмара", + "de": "Kukmor", + "ru": "Кукмор", + "ja": "クークモル" + }, + "lat": "56.18653", + "lon": "50.89721", + "id": 409487025, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "56.18861", + "lon": "50.88569", + "id": 10547 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Київ", + "enName": "Kyiv", + "names": { + "hi": "कीव", + "pt": "Kiev", + "prefix": "місто", + "hr": "Kijev", + "ht": "Kyèv", + "hu": "Kijev", + "lmo": "Kiev", + "xmf": "კიევი", + "yi": "קיעוו", + "hy": "Կիև", + "bar": "Kiew", + "nah": "Kiev", + "yo": "Kiev", + "pms": "Kijv", + "ia": "Kyiv", + "nan": "Kyyiv", + "id": "Kyiv", + "ie": "Kyiv", + "sco": "Kiev", + "scn": "Kiev", + "ext": "Kyiv", + "ab": "Кыив", + "qu": "Kiyiw", + "af": "Kiëf", + "pnb": "کیف", + "io": "Kyiv", + "frr": "Kyiv", + "is": "Kænugarður", + "it": "Kyiv", + "am": "ኪየቭ", + "an": "Kiev", + "zh": "基輔", + "ar": "كييف", + "jbo": "kiev", + "mhr": "Киев", + "ja": "キエフ", + "az": "Kiyev", + "zu": "IKiyevi", + "ro": "Kiev", + "ba": "Киев", + "be": "Кіеў", + "ru": "Киев", + "bg": "Киев", + "bi": "Kyiv", + "myv": "Киев ош", + "bn": "কিয়েভ", + "jv": "Kyiv", + "bo": "ཀིབ།", + "br": "Kyiv", + "sc": "Kiev", + "bs": "Kijev", + "se": "Kiova", + "sh": "Kijev", + "ka": "კიევი", + "sk": "Kyjev", + "roa-rup": "Kiev", + "sl": "Kijev", + "ca": "Kyiv", + "sq": "Kievi", + "sr": "Кијев", + "kk": "Киев", + "kl": "Kyiv", + "kn": "ಕೀವ್", + "sv": "Kiev", + "ko": "키예프", + "mrj": "Киев", + "sw": "Kiev", + "arz": "كييف", + "ku": "Kîev", + "kv": "Киев", + "ta": "கீவ்", + "ky": "Киев", + "cs": "Kyjev", + "te": "క్యివ్", + "cu": "Кꙑѥвъ", + "cv": "Кийӳ", + "tg": "Киев", + "th": "เคียฟ", + "la": "Kiovia", + "cy": "Kyiv", + "lb": "Kiew", + "tl": "Kiev", + "nds": "Kiew", + "da": "Kyiv", + "tr": "Kiev", + "tt": "Киев", + "be-tarask": "Кіеў", + "de": "Kiew", + "ln": "Kyjiw", + "ast": "Kiev", + "rue": "Київ", + "tw": "Kiev", + "hif": "Kiev", + "koi": "Киев", + "lt": "Kijevas", + "lv": "Kijeva", + "lij": "Kiev", + "lad": "Kyiv", + "ug": "كىيېۋ", + "roa-tara": "Kiev", + "vec": "Kiev", + "uk": "Київ", + "fiu-vro": "Kiiova", + "mi": "Kieu", + "ur": "کیف", + "mk": "Киев", + "pap": "Kiev", + "ml": "കീവ്", + "rmy": "Kiev", + "vep": "Kijev", + "mn": "Киев", + "mr": "क्यीव", + "uz": "Kiyev", + "ms": "Kiev", + "el": "Κίεβο", + "mt": "Kjiv", + "tzl": "Kíiv", + "als": "Kiew", + "eo": "Kievo", + "my": "ကီးယက်မြို့", + "ilo": "Kyiv", + "es": "Kyiv", + "mdf": "Киев", + "et": "Kiiev", + "eu": "Kyiv", + "dsb": "Kijew", + "vi": "Kiev", + "bat-smg": "Kijevs", + "hsb": "Kijew", + "vo": "Küyiv", + "fa": "کی‌یف", + "nl": "Kiev", + "udm": "Киев", + "nn": "Kiev", + "no": "Kiev", + "fi": "Kiova", + "yue": "基輔", + "fo": "Kyiv", + "fr": "Kyiv", + "gag": "Kyiv", + "fy": "Kiev", + "nov": "Kiyev", + "oc": "Kyiiv", + "crh": "Kiyev", + "wo": "Kiyew", + "ga": "Cív", + "ang": "Cænugeard", + "sah": "Киев", + "bxr": "Киев", + "gd": "Kyiv", + "os": "Киев", + "szl": "Kijůw", + "gl": "Kiev", + "war": "Kiev", + "bpy": "কিয়েভ", + "gv": "Kyiv", + "pa": "ਕੀਵ", + "csb": "Kyiv", + "cbk-zam": "Kyiv", + "ckb": "کیێڤ", + "pl": "Kijów", + "he": "קייב" + }, + "lat": "50.45011", + "lon": "30.52405", + "id": 26150422, + "type": "CITY", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Тетіїв", + "enName": "Tetiiv", + "names": { + "de": "Tetijiw", + "ru": "Тетиев", + "uk": "Тетіїв", + "prefix": "місто", + "pl": "Tetyjów" + }, + "lat": "49.36862", + "lon": "29.67712", + "id": 337561478, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Тетюши", + "enName": "Tetyushi", + "names": { + "tt": "Тәтеш", + "de": "Tetjuschi", + "cv": "Теччӗ", + "ru": "Тетюши", + "ja": "テチューシ" + }, + "lat": "54.93431", + "lon": "48.83168", + "id": 336524694, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.94122", + "lon": "48.81528", + "id": 6044 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Лаишево", + "enName": "Laishevo", + "names": { + "tt": "Лаеш", + "de": "Laischewo", + "cv": "Лаиш", + "ru": "Лаишево", + "ja": "ライシェヴォ", + "lt": "Laiševas" + }, + "lat": "55.40434", + "lon": "49.54684", + "id": 336524182, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.40922", + "lon": "49.54424", + "id": 2631 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Куркуль", + "names": { + "tt": "Кыркүл" + }, + "lat": "55.19469", + "lon": "50.10916", + "id": 826359259, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.19516", + "lon": "50.11581", + "id": 6495 + } + ], + "matchStreet": 1 + }, + { + "name": "Ржищів", + "enName": "Rzhyschiv", + "names": { + "de": "Rschyschtschiw", + "ru": "Ржищев", + "uk": "Ржищів", + "prefix": "місто", + "pl": "Rzyszczów" + }, + "lat": "49.96822", + "lon": "31.04118", + "id": 337538749, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Миронівка", + "enName": "Myronivka", + "names": { + "de": "Myroniwka", + "ru": "Мироновка", + "uk": "Миронівка", + "prefix": "місто", + "pl": "Mironówka" + }, + "lat": "49.65834", + "lon": "30.98250", + "id": 5119801550, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Березань", + "enName": "Berezan", + "names": { + "de": "Beresan", + "ru": "Березань", + "uk": "Березань", + "prefix": "місто", + "lt": "Berezanė", + "pl": "Berezań" + }, + "lat": "50.31327", + "lon": "31.46892", + "id": 337524664, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Чукмарлы", + "names": { + "tt": "Чукмарлы" + }, + "lat": "55.28447", + "lon": "52.43184", + "id": 583375597, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.28066", + "lon": "52.42903", + "id": 10262 + } + ], + "matchStreet": 1 + }, + { + "name": "Нижние Вязовые", + "enName": "Nizhniye Vyazovye", + "names": { + "tt": "Карамалы Тау", + "ja": "ニジニエ・ヴャゾヴイエ" + }, + "lat": "55.79963", + "lon": "48.52238", + "id": 752193024, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.79462", + "lon": "48.52856", + "id": 7858 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Вахоткино", + "lat": "56.00524", + "lon": "48.65201", + "id": 660899983, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "56.00694", + "lon": "48.64604", + "id": 11576 + } + ], + "matchStreet": 1 + }, + { + "name": "Осиново", + "names": { + "tt": "Осиново" + }, + "lat": "55.87730", + "lon": "48.89103", + "id": 722796219, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.87905", + "lon": "48.88858", + "id": 3983 + } + ], + "matchStreet": 1 + }, + { + "name": "Альметьевск", + "enName": "Almetyevsk", + "names": { + "tt": "Әлмәт", + "de": "Almetjewsk", + "be": "Альмецьеўск", + "ru": "Альметьевск", + "ko": "알메티옙스크", + "lt": "Almetjevskas", + "it": "Al'met'evsk", + "fr": "Almetievsk", + "es": "Almétievsk", + "zh": "阿尔梅季耶夫斯克", + "ar": "ألميتيفسك", + "cv": "Элмет", + "uk": "Альметьєвськ", + "hsb": "Almetjewsk", + "ja": "アリメチエフスク", + "az": "Əlmət", + "fa": "آلماتیفسک", + "pl": "Almietjewsk", + "ro": "Almetievsk", + "ba": "Әлмәт" + }, + "lat": "54.89965", + "lon": "52.30149", + "id": 2173235770, + "type": "CITY", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.91562", + "lon": "52.33893", + "id": 6097 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Апастово", + "enName": "Apastovo", + "names": { + "tt": "Апас", + "ar": "أباستوفو", + "de": "Apastowo", + "cv": "Апас", + "ru": "Апастово", + "ja": "アパソトヴォ", + "ca": "Apàstovo", + "ba": "Апас" + }, + "lat": "55.20267", + "lon": "48.50687", + "id": 703166491, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Балтаси", + "enName": "Baltasi", + "names": { + "tt": "Балтач", + "ru": "Балтаси", + "ja": "バルタシ" + }, + "lat": "56.34462", + "lon": "50.21142", + "id": 701721057, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Камское Устье", + "enName": "Kamskoye Ustye", + "names": { + "tt": "Кама Тамагы", + "cv": "Кама Вӑрри", + "ru": "Камское Устье", + "ja": "カムスコエ・ウスチエ" + }, + "lat": "55.20157", + "lon": "49.26802", + "id": 336524471, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.20141", + "lon": "49.26975", + "id": 7501 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Кагарлик", + "lat": "49.85635", + "lon": "30.74794", + "id": 6629021, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Коцюбинське", + "enName": "Kotsiubynske", + "names": { + "ru": "Коцюбинское", + "uk": "Коцюбинське", + "prefix": "селище міського типу", + "pl": "Kotsiubynske" + }, + "lat": "50.49047", + "lon": "30.33379", + "id": 337520861, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Рокитне", + "enName": "Rokytne", + "names": { + "de": "Rokytne", + "ru": "Ракитное", + "uk": "Рокитне", + "prefix": "селище міського типу", + "pl": "Rokitna" + }, + "lat": "49.68673", + "lon": "30.47303", + "id": 337549274, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Богуслав", + "enName": "Bohuslav", + "names": { + "de": "Bohuslaw", + "ru": "Богуслав", + "uk": "Богуслав", + "prefix": "місто", + "pl": "Bohusław" + }, + "lat": "49.54765", + "lon": "30.87330", + "id": 337555159, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Пестрецы", + "enName": "Pestretsy", + "names": { + "tt": "Питрәч", + "ru": "Пестрецы", + "ja": "ペストレツィ", + "lt": "Pestrecai" + }, + "lat": "55.75021", + "lon": "49.65709", + "id": 697241159, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.75617", + "lon": "49.64662", + "id": 2581 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Алексеевское", + "enName": "Alexeyevskoye", + "names": { + "tt": "Алексеевск", + "de": "Alexejewskoje", + "ru": "Алексеевское", + "ja": "アレクセーエフスコエ", + "lt": "Aleksėjevskojė" + }, + "lat": "55.30496", + "lon": "50.11289", + "id": 336524229, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "names": { + "tt": "Комсомоллар урамы" + }, + "lat": "55.30439", + "lon": "50.10965", + "id": 6105 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Мензелинск", + "enName": "Menzelinsk", + "names": { + "tt": "Минзәлә", + "de": "Menselinsk", + "ru": "Мензелинск", + "ja": "メンゼリンスク" + }, + "lat": "55.72556", + "lon": "53.10718", + "id": 337839017, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.72125", + "lon": "53.11864", + "id": 3881 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Бугульма", + "enName": "Bugulma", + "names": { + "tt": "Бөгелмә", + "de": "Bugulma", + "ru": "Бугульма", + "ja": "ブグリマ" + }, + "lat": "54.53841", + "lon": "52.79559", + "id": 1707207947, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.54021", + "lon": "52.76684", + "id": 2055 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Мамадыш", + "enName": "Mamadysh", + "names": { + "tt": "Мамадыш", + "de": "Mamadysch", + "ru": "Мамадыш", + "ja": "ママドィシ", + "az": "Mamadış" + }, + "lat": "55.70908", + "lon": "51.40891", + "id": 337839024, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.72513", + "lon": "51.40426", + "id": 8919 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Бавлы", + "enName": "Bavly", + "names": { + "tt": "Баулы", + "de": "Bawly", + "ru": "Бавлы", + "ja": "バヴルィ", + "et": "Bavlõ" + }, + "lat": "54.40309", + "lon": "53.23571", + "id": 337839279, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.40355", + "lon": "53.24687", + "id": 7026 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Згурівка", + "enName": "Zghurivka", + "names": { + "de": "Shuriwka", + "ru": "Згуровка", + "prefix": "селище міського типу", + "uk": "Згурівка", + "pl": "Zhuriwka" + }, + "lat": "50.49506", + "lon": "31.76918", + "id": 337520849, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Буинск", + "enName": "Buinsk", + "names": { + "tt": "Буа", + "de": "Buinsk", + "cv": "Пӑва", + "ru": "Буинск", + "ja": "ブインスク" + }, + "lat": "54.97229", + "lon": "48.29289", + "id": 295862317, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.98741", + "lon": "48.29586", + "id": 5979 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Гостомель", + "enName": "Hostomel", + "names": { + "ru": "Гостомель", + "uk": "Гостомель", + "prefix": "селище міського типу", + "pl": "Hostomel" + }, + "lat": "50.58826", + "lon": "30.25909", + "id": 337519314, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Бровари", + "enName": "Brovary", + "names": { + "de": "Browary", + "ru": "Бровары", + "prefix": "місто", + "uk": "Бровари", + "eo": "Brovari", + "fr": "Brovary", + "pl": "Browary", + "hu": "Brovari", + "et": "Brovarõ", + "sr": "Бровари" + }, + "lat": "50.51111", + "lon": "30.79004", + "id": 3673183717, + "type": "CITY", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Победа", + "names": { + "tt": "Победа" + }, + "lat": "54.67300", + "lon": "52.66657", + "id": 783002941, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "54.67386", + "lon": "52.66232", + "id": 4542 + } + ], + "matchStreet": 1 + }, + { + "name": "Димер", + "enName": "Dymer", + "names": { + "ru": "Дымер", + "uk": "Димер", + "prefix": "селище міського типу", + "pl": "Dymer" + }, + "lat": "50.78355", + "lon": "30.30780", + "id": 1585808480, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Набережные Челны", + "enName": "Naberezhnye Chelny", + "names": { + "tt": "Яр Чаллы", + "de": "Nabereschnyje Tschelny", + "no": "Naberezjnyje Tsjelny", + "be": "Наберажныя Чалны", + "fi": "Naberežnyje Tšelny", + "ru": "Набережные Челны", + "pt": "Naberejnye Chelny", + "bg": "Набережние Челни", + "lt": "Naberežnyje Čelnai", + "hr": "Naberežnye Čelny", + "lv": "Naberežnije Čelni", + "fr": "Naberejnye Tchelny", + "ka": "ნაბერეჟნიე ჩელნი", + "uk": "Набережні Човни", + "ca": "Nàberejnie_Txelní", + "sr": "Набережније Челни", + "sv": "Naberezjnyje Tjelny", + "ko": "나베레즈니예첼니", + "it": "Naberežnye Čelny", + "es": "Náberezhnye Chelny", + "zh": "卡马河畔切尔尼", + "et": "Naberežnõje Tšelnõ", + "cs": "Naberežnyje Čelny", + "ar": "نابريجناي تشلني", + "cv": "Чаллă", + "ja": "ナベレジヌイェ・チェルヌイ", + "az": "Naberejnıye Çelnı", + "fa": "نابرژنیه چلنی", + "pl": "Nabierieżnyje Czełny", + "ro": "Naberejnîe Celnî", + "nl": "Naberezjnye Tsjelny", + "tr": "Yarçallı", + "ba": "Яр Саллы" + }, + "lat": "55.74201", + "lon": "52.39923", + "id": 191749240, + "type": "CITY", + "listOfStreets": [ + { + "name": "Комсомольская набережная", + "lat": "55.69179", + "lon": "52.29220", + "id": 1022 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Сквира", + "enName": "Skvyra", + "names": { + "kk": "Skvyra", + "de": "Skwyra", + "ceb": "Skvyra", + "be": "Сквыра", + "ru": "Сквира", + "sv": "Skvyra", + "prefix": "місто", + "it": "Skvyra", + "fr": "Skvyra", + "cs": "Skvyra", + "hy": "Սկվյրա", + "vi": "Skvyra", + "uk": "Сквира", + "sk": "Skvyra", + "fa": "اسکویرا", + "pl": "Skwyra", + "ro": "Skvîra" + }, + "lat": "49.73370", + "lon": "29.66266", + "id": 337547206, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Рыбная Слобода", + "enName": "Rybnaya Sloboda", + "names": { + "tt": "Балык Бистәсе", + "ru": "Рыбная Слобода", + "ja": "ルィブナヤ・スロボーダ" + }, + "lat": "55.46442", + "lon": "50.14061", + "id": 703680750, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "55.45858", + "lon": "50.13169", + "id": 10141 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Арск", + "enName": "Arsk", + "names": { + "tt": "Арча", + "de": "Arsk", + "ru": "Арск", + "ja": "アルスク" + }, + "lat": "56.09099", + "lon": "49.87718", + "id": 295868224, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Комсомольская улица", + "lat": "56.08784", + "lon": "49.86229", + "id": 6776 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Тюлячи", + "enName": "Tyulyachi", + "names": { + "tt": "Теләче", + "ru": "Тюлячи", + "ja": "チュリャチ" + }, + "lat": "55.89188", + "lon": "50.23350", + "id": 697241170, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Васильків", + "enName": "Vasylkiv", + "names": { + "de": "Wassylkiw", + "ru": "Васильков", + "uk": "Васильків", + "prefix": "місто", + "fr": "Vassylkiv", + "pl": "Wasilków", + "hu": "Vasylkiv" + }, + "lat": "50.17387", + "lon": "30.32158", + "id": 337527490, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Красный Ключ", + "enName": "Krasny Klyuch", + "names": { + "tt": "Красный Ключ", + "ja": "クラスヌイ・クリューチ" + }, + "lat": "55.68249", + "lon": "51.81914", + "id": 585272830, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + } + ] +} \ No newline at end of file diff --git a/OsmAnd-java/src/test/resources/search/street_lenina_30.json b/OsmAnd-java/src/test/resources/search/street_lenina_30.json new file mode 100644 index 0000000000..2c510ea1e0 --- /dev/null +++ b/OsmAnd-java/src/test/resources/search/street_lenina_30.json @@ -0,0 +1,8626 @@ +{ + "settings": { + "lat": "55.28666", + "lon": "52.00558", + "radiusLevel": 1, + "totalLimit": -1, + "lang": "", + "transliterateIfMissing": false, + "emptyQueryAllowed": false, + "sortByName": false + }, + "phrase": "ленина 30", + "results": [ + "30А, улица Ленина, Заинск", + "30Б, улица Ленина, Заинск", + "30, улица Ленина, Сарманово", + "30, улица Ленина, Большое Афанасово", + "30, улица Ленина, Альметьевск", + "30А, улица Ленина, Альметьевск", + "улица Ленина, Заинск", + "улица Ленина, Аксарино", + "улица Ленина, Светлое Озеро" + ], + "amenities": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "ru": "улица Ленина", + "be": "вулiца Ленiна", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.26804", + "lon": "50.85634", + "id": 712954141, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "bridge_car": "yes" + } + }, + { + "name": "улица Ленина", + "lat": "55.25628", + "lon": "51.75138", + "id": 1168759757, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "surface_gravel": "gravel", + "bridge_car": "yes" + } + }, + { + "name": "Дом-музей В.И.Ленина", + "lat": "55.78690", + "lon": "49.13873", + "id": 345097079, + "subType": "museum", + "type": "tourism" + }, + { + "name": "Дом-музей В.И.Ленина", + "lat": "55.78690", + "lon": "49.13873", + "id": 345097079, + "subType": "building", + "type": "man_made" + }, + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "ru": "улица Ленина", + "be": "вулiца Ленiна", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.33029", + "lon": "50.80344", + "id": 714393277, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "bridge_car": "yes", + "ref": "16К-1014" + } + }, + { + "name": "Ленина 148", + "lat": "56.34420", + "lon": "50.23104", + "id": 10550243242, + "subType": "wilderness_hut", + "type": "tourism" + }, + { + "name": "совхоза имени Ленина", + "enName": "Sovkhoza imeni Lenina", + "names": { + "ru": "совхоза имени Ленина" + }, + "lat": "55.58691", + "lon": "37.73102", + "id": -627450, + "subType": "town", + "type": "administrative", + "additionalInfo": { + "population": "4742" + } + }, + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "ru": "улица Ленина", + "be": "вулiца Ленiна", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.30894", + "lon": "50.10678", + "id": 346283479, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "bridge_car": "yes" + } + }, + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "ru": "улица Ленина", + "be": "вулiца Ленiна", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.51206", + "lon": "51.81751", + "id": 482839291, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "bridge_car": "yes" + } + }, + { + "name": "улица Ленина", + "lat": "54.89796", + "lon": "52.28482", + "id": 9047976566, + "subType": "clock", + "type": "tourism", + "additionalInfo": { + "date_yes": "yes", + "support_pole": "pole", + "display_digital_yes": "yes", + "visibility_street": "street", + "start_date": "2016" + } + }, + { + "name": "улица Ленина", + "lat": "54.89796", + "lon": "52.28482", + "id": 9047976566, + "subType": "monitoring_station", + "type": "man_made", + "additionalInfo": { + "date_yes": "yes", + "support_pole": "pole", + "display_digital_yes": "yes", + "visibility_street": "street", + "start_date": "2016" + } + }, + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "ru": "улица Ленина", + "be": "вулiца Ленiна", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.21664", + "lon": "48.36718", + "id": 311139765, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "bridge_car": "yes" + } + }, + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "ru": "улица Ленина", + "be": "вулiца Ленiна", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.98256", + "lon": "50.38562", + "id": 603647995, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "bridge_car": "yes" + } + }, + { + "name": "ДК им.Ленина", + "names": { + "tt": "Ленин исемендәге мәдәният һәм ял комплексы" + }, + "lat": "55.85446", + "lon": "49.08569", + "id": 1981869798, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "6, 18, 42, 60, 76", + "operator": "ПАТП-2, ПАТП-4" + } + }, + { + "name": "Парк «Крылья Советов»", + "names": { + "tt": "«Крылья Советов» паркы", + "ru": "Парк «Крылья Советов»" + }, + "lat": "55.85330", + "lon": "49.09005", + "id": 81482207, + "subType": "park", + "type": "entertainment", + "additionalInfo": { + "wikipedia": "http://ru.wikipedia.org/wiki/Крылья Советов (парк)", + "old_name": "Парк Ленина" + } + }, + { + "name": "Культурно-досуговый комплекс им. В.И.Ленина", + "lat": "55.85390", + "lon": "49.08687", + "id": 145151087, + "subType": "community_centre", + "type": "entertainment", + "additionalInfo": { + "website": "https://vk.com/kdklenin" + } + }, + { + "name": "Культурно-досуговый комплекс им. В.И.Ленина", + "lat": "55.85390", + "lon": "49.08687", + "id": 145151087, + "subType": "building", + "type": "man_made", + "additionalInfo": { + "website": "https://vk.com/kdklenin" + } + }, + { + "name": "Ленина", + "lat": "55.83146", + "lon": "48.66248", + "id": 899597590, + "subType": "public_transport_stop_position", + "type": "transportation" + }, + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "ru": "улица Ленина", + "be": "вулiца Ленiна", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.51247", + "lon": "52.99594", + "id": 620696879, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "bridge_car": "yes" + } + }, + { + "name": "НПО им. Ленина", + "lat": "55.86587", + "lon": "48.86088", + "id": 4776241786, + "subType": "suburb", + "type": "administrative" + }, + { + "name": "Ул Ленина", + "lat": "55.93939", + "lon": "52.25632", + "id": 10608626446, + "subType": "bus_stop", + "type": "transportation" + }, + { + "name": "НПО им. Ленина", + "lat": "55.86505", + "lon": "48.86116", + "id": 116563497, + "subType": "suburb", + "type": "administrative", + "additionalInfo": { + "rural": "rural" + } + }, + { + "name": "НПО им. Ленина", + "lat": "55.86505", + "lon": "48.86116", + "id": 116563497, + "subType": "residential", + "type": "administrative", + "additionalInfo": { + "rural": "rural" + } + }, + { + "name": "Леніна", + "enName": "Lenina", + "names": { + "ru": "Ленина", + "uk": "Леніна" + }, + "lat": "48.43517", + "lon": "39.14890", + "id": -707666, + "subType": "town", + "type": "administrative", + "additionalInfo": { + "population": "1083" + } + }, + { + "name": "Санаторий имени В.И. Ленина", + "lat": "54.59147", + "lon": "48.40525", + "id": 399567309, + "subType": "resort", + "type": "tourism", + "additionalInfo": { + "sanatorium": "sanatorium", + "operator": "ОАО \"Ульяновсккурорт\"" + } + }, + { + "name": "ДК им.Ленина", + "names": { + "tt": "Ленин исемендәге мәдәният һәм ял комплексы" + }, + "lat": "55.85434", + "lon": "49.08668", + "id": 1981869822, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "42, 93", + "operator": "ПАТП-4" + } + }, + { + "name": "Парк Ленина", + "lat": "56.18713", + "lon": "50.89041", + "id": 705187927, + "subType": "park", + "type": "entertainment" + } + ], + "cities": [ + { + "name": "Тараща", + "enName": "Tarascha", + "names": { + "de": "Taraschtscha", + "ru": "Тараща", + "uk": "Тараща", + "prefix": "місто", + "pl": "Taraszcza" + }, + "lat": "49.55550", + "lon": "30.50234", + "id": 620750304, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Сарманово", + "enName": "Sarmanovo", + "names": { + "tt": "Сарман", + "de": "Sarmanowo", + "ru": "Сарманово", + "ja": "サルマノヴォ" + }, + "lat": "55.25225", + "lon": "52.58872", + "id": 337839105, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.24364", + "lon": "52.57943", + "id": 1777, + "buildings": [ + { + "name": "1", + "lat": "55.24647", + "lon": "52.58063" + }, + { + "name": "2В", + "lat": "55.24580", + "lon": "52.58254" + }, + { + "name": "12А", + "lat": "55.24959", + "lon": "52.58672" + }, + { + "name": "17", + "lat": "55.24952", + "lon": "52.58473" + }, + { + "name": "20", + "lat": "55.25097", + "lon": "52.58722" + }, + { + "name": "22", + "lat": "55.25134", + "lon": "52.58775" + }, + { + "name": "24", + "lat": "55.25156", + "lon": "52.58810" + }, + { + "name": "26", + "lat": "55.25185", + "lon": "52.58900" + }, + { + "name": "28Б", + "lat": "55.25162", + "lon": "52.59093" + }, + { + "name": "28Б", + "lat": "55.25192", + "lon": "52.59042" + }, + { + "name": "30", + "lat": "55.25225", + "lon": "52.58949" + }, + { + "name": "31", + "lat": "55.25155", + "lon": "52.58679" + }, + { + "name": "32", + "lat": "55.25298", + "lon": "52.58996" + }, + { + "name": "33", + "lat": "55.25203", + "lon": "52.58741" + }, + { + "name": "34", + "lat": "55.25328", + "lon": "52.59018" + }, + { + "name": "35", + "lat": "55.25260", + "lon": "52.58801" + }, + { + "name": "37", + "lat": "55.25320", + "lon": "52.58840" + }, + { + "name": "40", + "lat": "55.25361", + "lon": "52.59050", + "postcode": "423350" + }, + { + "name": "42", + "lat": "55.25394", + "lon": "52.59091", + "postcode": "423350" + }, + { + "name": "44", + "lat": "55.25435", + "lon": "52.59149", + "postcode": "423350" + }, + { + "name": "46", + "lat": "55.25449", + "lon": "52.59181", + "postcode": "423350" + }, + { + "name": "48", + "lat": "55.25470", + "lon": "52.59190" + }, + { + "name": "50", + "lat": "55.25491", + "lon": "52.59228" + }, + { + "name": "52", + "lat": "55.25532", + "lon": "52.59275" + }, + { + "name": "58", + "lat": "55.25519", + "lon": "52.59449" + }, + { + "name": "60", + "lat": "55.25554", + "lon": "52.59404" + }, + { + "name": "62", + "lat": "55.25602", + "lon": "52.59359" + }, + { + "name": "63", + "lat": "55.25674", + "lon": "52.59177" + }, + { + "name": "65", + "lat": "55.25629", + "lon": "52.59282" + } + ], + "intersectedStreets": [ + { + "name": "улица Хайруллина", + "lat": "55.23851", + "lon": "52.57400" + }, + { + "name": "улица Рафикова", + "lat": "55.23939", + "lon": "52.57497" + }, + { + "name": "улица Татарстана", + "names": { + "tt": "Татарстан урамы" + }, + "lat": "55.24022", + "lon": "52.57587" + }, + { + "name": "улица Х. Туфана", + "lat": "55.24104", + "lon": "52.57673" + }, + { + "name": "улица З. Хасана", + "lat": "55.24187", + "lon": "52.57756" + }, + { + "name": "улица М. Тинчурина", + "lat": "55.24281", + "lon": "52.57855" + }, + { + "name": "улица Гаяза Исхаки", + "lat": "55.24364", + "lon": "52.57943" + }, + { + "name": "Советская улица", + "lat": "55.24519", + "lon": "52.58100" + }, + { + "name": "улица Строителей", + "lat": "55.24686", + "lon": "52.58256" + }, + { + "name": "улица Яруллина", + "lat": "55.24874", + "lon": "52.58439" + }, + { + "name": "улица Терешковой", + "lat": "55.25358", + "lon": "52.59007" + }, + { + "name": "улица Мусы Джалиля", + "lat": "55.26035", + "lon": "52.60063" + }, + { + "name": "улица Ямашева", + "lat": "55.26347", + "lon": "52.60303" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Богатые Сабы", + "enName": "Bogatye Saby", + "names": { + "tt": "Байлар Сабасы", + "ru": "Богатые Сабы", + "ja": "ボガートィエ・サブィ" + }, + "lat": "56.01062", + "lon": "50.44677", + "id": 701847474, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.01590", + "lon": "50.46038", + "id": 8825 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Прип’ять", + "enName": "Pripyat", + "names": { + "de": "Prypjat", + "no": "Pripjat", + "be": "Прып'яць", + "fi": "Prypjat", + "ru": "Припять", + "pt": "Pripyat", + "bg": "Припя̀т", + "prefix": "місто", + "lt": "Pripetė", + "hr": "Prėpetė", + "fr": "Pripiat", + "hu": "Pripjaty", + "uk": "Прип’ять", + "sk": "Pripjať", + "sl": "Pripjat", + "id": "Prypiat", + "pam": "Prypiat", + "ca": "Pripyat", + "sr": "Припјат", + "sv": "Pripjat", + "ko": "프리피야티", + "eo": "Pripjat", + "it": "Pripjat", + "es": "Prípiat", + "zh": "普里皮亚特", + "et": "Prõpjat", + "cs": "Pripjať", + "ja": "プリピャチ", + "pl": "Prypeć", + "da": "Prypiat", + "ro": "Pripiat", + "nl": "Prypjat" + }, + "lat": "51.40613", + "lon": "30.05711", + "id": 127835001, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Актаныш", + "enName": "Aktanysh", + "names": { + "tt": "Актаныш", + "ru": "Актаныш", + "ja": "アクタヌィシ" + }, + "lat": "55.72218", + "lon": "54.05773", + "id": 606106631, + "type": "TOWN", + "listOfStreets": [ + { + "name": "проспект Ленина", + "lat": "55.72025", + "lon": "54.06795", + "id": 681 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Баришівка", + "enName": "Baryshivka", + "names": { + "de": "Baryschiwka", + "ru": "Барышевка", + "uk": "Баришівка", + "prefix": "селище міського типу", + "pl": "Baryszówka" + }, + "lat": "50.36453", + "lon": "31.32567", + "id": 337523450, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Чорнобиль", + "enName": "Chornobyl", + "names": { + "nn": "Tsjernobyl", + "de": "Tschornobyl", + "no": "Tsjernobyl", + "be": "Чарнобыль", + "fi": "Tšernobyl", + "ru": "Чернобыль", + "pt": "Chernobyl", + "prefix": "місто", + "lt": "Černobylis", + "hr": "Černobil", + "fr": "Tchernobyl", + "hu": "Csernobil", + "yi": "טשערנאבל", + "oc": "Chornobyl", + "uk": "Чорнобиль", + "sk": "Černobyľ", + "sl": "Černobil", + "id": "Chernobyl", + "ca": "Txernòbil", + "scn": "Chernobyl", + "sr": "Černobilj", + "sv": "Tjernobyl", + "ko": "초르노빌", + "af": "Černobylis", + "ms": "Chernobyl", + "eo": "Ĉernobilo", + "it": "Černobyl", + "es": "Chernóbil", + "zh": "切尔诺贝利", + "et": "Tšornobõl", + "cs": "Černobyl", + "eu": "Txernobyl", + "ar": "تشيرنوبيل", + "vi": "Chernobyl", + "lb": "Tschernobyl", + "ja": "チョルノーブィリ", + "pl": "Czarnobyl", + "da": "Tjernobyl", + "he": "צ'רנוביל", + "ro": "Cernobîl", + "nl": "Tsjernobyl", + "tr": "Çernobil" + }, + "lat": "51.27053", + "lon": "30.21944", + "id": 127834997, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "НПО им. Ленина", + "lat": "55.86587", + "lon": "48.86088", + "id": 2388120893, + "type": "SUBURB", + "listOfStreets": [], + "matchCity": 1 + }, + { + "name": "Узин", + "enName": "Uzyn", + "names": { + "ru": "Узин", + "uk": "Узин", + "prefix": "місто", + "eo": "Uzin", + "fr": "Ouzyn", + "pl": "Uzyn" + }, + "lat": "49.82847", + "lon": "30.41973", + "id": 337543666, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Камские Поляны", + "enName": "Kamskiye Polyani", + "names": { + "tt": "Кама Аланы", + "de": "Kamskije Poljany", + "ru": "Камские Поляны", + "ja": "カムスキエ・ポリャヌィ" + }, + "lat": "55.42550", + "lon": "51.41016", + "id": 587614698, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Малая Шильна", + "names": { + "tt": "Кече Шилнә" + }, + "lat": "55.80199", + "lon": "52.53396", + "id": 593611093, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.80198", + "lon": "52.53385", + "id": 8882, + "buildings": [ + { + "name": "10", + "lat": "55.80697", + "lon": "52.53375" + }, + { + "name": "23", + "lat": "55.80428", + "lon": "52.53360" + } + ], + "intersectedStreets": [ + { + "name": "Солнечная улица", + "lat": "55.79622", + "lon": "52.53536" + }, + { + "name": "Родниковая улица", + "lat": "55.79746", + "lon": "52.53497" + }, + { + "name": "Центральная улица", + "lat": "55.80198", + "lon": "52.53385" + }, + { + "name": "улица Карла Маркса", + "lat": "55.80718", + "lon": "52.53402" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Малая Цильна", + "names": { + "tt": "Кече Чынлы" + }, + "lat": "54.76570", + "lon": "47.86951", + "id": 761955165, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.76848", + "lon": "47.85664", + "id": 10999 + } + ], + "matchStreet": 1 + }, + { + "name": "Бабинці", + "enName": "Babyntsi", + "names": { + "ru": "Бабинцы", + "uk": "Бабинці", + "prefix": "селище міського типу", + "pl": "Babyntsi" + }, + "lat": "50.64241", + "lon": "30.02907", + "id": 1668578377, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Славутич", + "enName": "Slavutych", + "names": { + "de": "Slawutytsch", + "ru": "Славутич", + "uk": "Славутич", + "prefix": "місто", + "lt": "Slavutičius", + "pl": "Sławutycz" + }, + "lat": "51.52014", + "lon": "30.75623", + "id": 337507300, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Иннополис", + "enName": "Innopolis", + "names": { + "tt": "Иннополис", + "ru": "Иннополис" + }, + "lat": "55.75226", + "lon": "48.74458", + "id": 2294499518, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Измери", + "names": { + "tt": "Әҗмәр" + }, + "lat": "55.13665", + "lon": "49.53504", + "id": 787285204, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы" + }, + "lat": "55.13857", + "lon": "49.53388", + "id": 11882 + } + ], + "matchStreet": 1 + }, + { + "name": "Лащ-Таяба", + "names": { + "cv": "Лаш Таяпа", + "ru": "Лащ-Таяба" + }, + "lat": "55.03009", + "lon": "47.99935", + "id": 795358551, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "55.03053", + "lon": "47.99341", + "id": 11865 + } + ], + "matchStreet": 1 + }, + { + "name": "Вишневе", + "enName": "Vyshneve", + "names": { + "de": "Wyschnewe", + "ru": "Вишнёвое", + "uk": "Вишневе", + "prefix": "місто", + "pl": "Wysznewe", + "hu": "Vyshneve" + }, + "lat": "50.39175", + "lon": "30.36791", + "id": 36507630, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Боярка", + "enName": "Boiarka", + "names": { + "ru": "Боярка", + "uk": "Боярка", + "prefix": "місто", + "pl": "Bojarka", + "hu": "Boiarka" + }, + "lat": "50.33567", + "lon": "30.28476", + "id": 36507632, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Васильево", + "enName": "Vasilyevo", + "names": { + "tt": "Васильево", + "de": "Wassiljewo", + "ru": "Васильево", + "ja": "ヴァシーリエヴォ" + }, + "lat": "55.83729", + "lon": "48.67924", + "id": 336524045, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.83157", + "lon": "48.65047", + "id": 71 + } + ], + "matchStreet": 1 + }, + { + "name": "Волжск", + "enName": "Volzhsk", + "names": { + "tt": "Волжск", + "de": "Wolschsk", + "ru": "Волжск", + "chm": "Юлсер-Ола", + "eo": "Volĵsk", + "it": "Volžsk", + "fr": "Voljsk", + "cs": "Volžsk", + "cv": "Волжск", + "uk": "Волзьк", + "mhr": "Юлсер-Ола", + "ja": "ヴォルシスク", + "pl": "Wołżsk", + "tr": "Voljsk" + }, + "lat": "55.86366", + "lon": "48.36186", + "id": 336523790, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.86402", + "lon": "48.35956", + "id": 14 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Бориспіль", + "enName": "Boryspil", + "names": { + "de": "Boryspil", + "ru": "Борисполь", + "uk": "Бориспіль", + "prefix": "місто", + "eo": "Borispilo", + "fr": "Boryspil", + "pl": "Boryspol", + "hu": "Boryspil" + }, + "lat": "50.35121", + "lon": "30.95076", + "id": 26150796, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Клавдієво-Тарасове", + "enName": "Klavdiievo-Tarasove", + "names": { + "ru": "Клавдиево-Тарасово", + "uk": "Клавдієво-Тарасове", + "prefix": "селище міського типу", + "pl": "Klavdiievo-Tarasove" + }, + "lat": "50.58414", + "lon": "30.00950", + "id": 337519388, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Гребінки", + "enName": "Hrebinky", + "names": { + "ru": "Гребёнки", + "uk": "Гребінки", + "prefix": "селище міського типу", + "pl": "Hrebinky" + }, + "lat": "49.95346", + "lon": "30.17635", + "id": 337539356, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Большое Мереткозино", + "names": { + "tt": "Олы Мәрәтхуҗа" + }, + "lat": "55.23953", + "lon": "49.07874", + "id": 723847137, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы" + }, + "lat": "55.23875", + "lon": "49.08286", + "id": 11838 + } + ], + "matchStreet": 1 + }, + { + "name": "Чистополь", + "enName": "Chistopol", + "names": { + "tt": "Чистай", + "de": "Tschistopol", + "cv": "Чистай", + "ru": "Чистополь", + "ja": "チストポリ" + }, + "lat": "55.37148", + "lon": "50.63674", + "id": 295864677, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.37059", + "lon": "50.64558", + "id": 234 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Чупаево", + "names": { + "tt": "Чупай" + }, + "lat": "54.79709", + "lon": "52.13289", + "id": 867304315, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.79555", + "lon": "52.12682", + "id": 6365, + "intersectedStreets": [ + { + "name": "улица Кирова", + "names": { + "tt": "Киров урамы" + }, + "lat": "54.79539", + "lon": "52.12725" + }, + { + "name": "Советская улица", + "lat": "54.79425", + "lon": "52.13004" + }, + { + "name": "улица Тукая", + "lat": "54.79326", + "lon": "52.13470" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Малые Кабаны", + "names": { + "tt": "Кече Кабан" + }, + "lat": "55.65275", + "lon": "49.28042", + "id": 760312409, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.64963", + "lon": "49.27699", + "id": 10859 + } + ], + "matchStreet": 1 + }, + { + "name": "Ірпінь", + "enName": "Irpin", + "names": { + "de": "Irpin", + "ru": "Ирпень", + "uk": "Ірпінь", + "prefix": "місто", + "fr": "Irpine", + "pl": "Irpień", + "hu": "Irpin" + }, + "lat": "50.51665", + "lon": "30.25004", + "id": 36505064, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Починок Сутер", + "names": { + "tt": "Пүчинкә-Сутер" + }, + "lat": "56.06771", + "lon": "51.02679", + "id": 797048460, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.06966", + "lon": "51.03001", + "id": 6864 + } + ], + "matchStreet": 1 + }, + { + "name": "Заинск", + "enName": "Zainsk", + "names": { + "tt": "Зәй", + "de": "Sainsk", + "ru": "Заинск", + "ja": "ザインスク" + }, + "lat": "55.28665", + "lon": "52.00561", + "id": 337839081, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.28712", + "lon": "51.99900", + "id": 730, + "buildings": [ + { + "name": "1", + "lat": "55.28849", + "lon": "52.00003", + "postcode": "423520" + }, + { + "name": "1А", + "lat": "55.29061", + "lon": "52.00093", + "postcode": "423520" + }, + { + "name": "1Б", + "lat": "55.29008", + "lon": "52.00082", + "postcode": "423520" + }, + { + "name": "1В", + "lat": "55.28956", + "lon": "52.00072" + }, + { + "name": "1Г", + "lat": "55.28980", + "lon": "52.00022", + "postcode": "423520" + }, + { + "name": "2", + "lat": "55.28615", + "lon": "51.99898", + "postcode": "423520" + }, + { + "name": "2А", + "lat": "55.28889", + "lon": "51.99926", + "postcode": "423520" + }, + { + "name": "2Б", + "lat": "55.28988", + "lon": "51.99958" + }, + { + "name": "2Г", + "lat": "55.28738", + "lon": "51.99911" + }, + { + "name": "2Д", + "lat": "55.28712", + "lon": "51.99900" + }, + { + "name": "2Д", + "lat": "55.28713", + "lon": "51.99900" + }, + { + "name": "3", + "lat": "55.28769", + "lon": "52.00014" + }, + { + "name": "4", + "lat": "55.28574", + "lon": "51.99887", + "postcode": "423520" + }, + { + "name": "5", + "lat": "55.28701", + "lon": "51.99973" + }, + { + "name": "6", + "lat": "55.28530", + "lon": "51.99878", + "postcode": "423520" + }, + { + "name": "6А", + "lat": "55.28503", + "lon": "51.99799" + }, + { + "name": "7", + "lat": "55.28578", + "lon": "51.99947", + "postcode": "423520" + }, + { + "name": "7А", + "lat": "55.28545", + "lon": "52.00011", + "postcode": "423520" + }, + { + "name": "7Б", + "lat": "55.28551", + "lon": "51.99945", + "postcode": "423520" + }, + { + "name": "8", + "lat": "55.28470", + "lon": "51.99859", + "postcode": "423520" + }, + { + "name": "9", + "lat": "55.28487", + "lon": "51.99930", + "postcode": "423520" + }, + { + "name": "9А", + "lat": "55.28508", + "lon": "52.00005" + }, + { + "name": "9Б", + "lat": "55.28364", + "lon": "51.99941" + }, + { + "name": "9Б", + "lat": "55.28354", + "lon": "51.99913", + "postcode": "423520" + }, + { + "name": "10", + "lat": "55.28427", + "lon": "51.99855", + "postcode": "423520" + }, + { + "name": "10А", + "lat": "55.28413", + "lon": "51.99821" + }, + { + "name": "10Б", + "lat": "55.28436", + "lon": "51.99818" + }, + { + "name": "11", + "lat": "55.28221", + "lon": "51.99876", + "postcode": "423520" + }, + { + "name": "11А", + "lat": "55.28189", + "lon": "51.99941" + }, + { + "name": "12", + "lat": "55.28384", + "lon": "51.99846", + "postcode": "423520" + }, + { + "name": "13", + "lat": "55.28167", + "lon": "51.99868", + "postcode": "423520" + }, + { + "name": "14", + "lat": "55.28331", + "lon": "51.99838", + "postcode": "423520" + }, + { + "name": "15", + "lat": "55.28129", + "lon": "51.99857", + "postcode": "423520" + }, + { + "name": "15А", + "lat": "55.28151", + "lon": "51.99934" + }, + { + "name": "16А", + "lat": "55.28289", + "lon": "51.99814" + }, + { + "name": "17", + "lat": "55.28023", + "lon": "51.99823", + "postcode": "423520" + }, + { + "name": "18", + "lat": "55.28245", + "lon": "51.99823", + "postcode": "423520" + }, + { + "name": "18А", + "lat": "55.28217", + "lon": "51.99743" + }, + { + "name": "19", + "lat": "55.27951", + "lon": "51.99827", + "postcode": "423520" + }, + { + "name": "19А", + "lat": "55.27863", + "lon": "51.99808", + "postcode": "423520" + }, + { + "name": "19А", + "lat": "55.27892", + "lon": "51.99821" + }, + { + "name": "19Б", + "lat": "55.27912", + "lon": "51.99816", + "postcode": "423520" + }, + { + "name": "20", + "lat": "55.28185", + "lon": "51.99810", + "postcode": "423520" + }, + { + "name": "21", + "lat": "55.27742", + "lon": "51.99795" + }, + { + "name": "22", + "lat": "55.28143", + "lon": "51.99801" + }, + { + "name": "23", + "lat": "55.27684", + "lon": "51.99771" + }, + { + "name": "23А", + "lat": "55.27714", + "lon": "51.99893" + }, + { + "name": "24", + "lat": "55.28099", + "lon": "51.99793" + }, + { + "name": "25", + "lat": "55.27600", + "lon": "51.99754" + }, + { + "name": "25А", + "lat": "55.27632", + "lon": "51.99866" + }, + { + "name": "26", + "lat": "55.28037", + "lon": "51.99778" + }, + { + "name": "26А", + "lat": "55.27968", + "lon": "51.99715" + }, + { + "name": "26Г/1", + "lat": "55.27982", + "lon": "51.99602" + }, + { + "name": "26Г/2", + "lat": "55.28017", + "lon": "51.99600", + "postcode": "423520" + }, + { + "name": "27", + "lat": "55.27534", + "lon": "51.99859" + }, + { + "name": "27А", + "lat": "55.27566", + "lon": "52.00093" + }, + { + "name": "27Б", + "lat": "55.27607", + "lon": "52.00200" + }, + { + "name": "28", + "lat": "55.27761", + "lon": "51.99718" + }, + { + "name": "28А", + "lat": "55.27859", + "lon": "51.99737" + }, + { + "name": "29", + "lat": "55.27486", + "lon": "51.99735" + }, + { + "name": "29А", + "lat": "55.27511", + "lon": "51.99840" + }, + { + "name": "29Б", + "lat": "55.27492", + "lon": "51.99930" + }, + { + "name": "30Б", + "lat": "55.27657", + "lon": "51.99698" + }, + { + "name": "30А", + "lat": "55.27579", + "lon": "51.99679", + "postcode": "423520" + }, + { + "name": "31", + "lat": "55.27457", + "lon": "51.99833" + }, + { + "name": "32", + "lat": "55.27589", + "lon": "51.99524" + }, + { + "name": "34", + "lat": "55.27500", + "lon": "51.99664" + }, + { + "name": "34А", + "lat": "55.27530", + "lon": "51.99503" + }, + { + "name": "36", + "lat": "55.27461", + "lon": "51.99533" + } + ], + "intersectedStreets": [ + { + "name": "проспект Нефтяников", + "lat": "55.28642", + "lon": "51.99932" + }, + { + "name": "улица Лобачевского", + "lat": "55.28428", + "lon": "51.99887" + }, + { + "name": "улица Островского", + "lat": "55.28356", + "lon": "51.99872" + }, + { + "name": "улица Крупской", + "lat": "55.28284", + "lon": "51.99857" + }, + { + "name": "переулок Строителей", + "lat": "55.28265", + "lon": "51.99853" + }, + { + "name": "Комсомольская улица", + "lat": "55.28069", + "lon": "51.99812" + }, + { + "name": "Казанская улица", + "names": { + "tt": "Казан урамы", + "tt-lat": "Qazan uramı" + }, + "lat": "55.28885", + "lon": "51.99975" + }, + { + "name": "улица Никифорова", + "lat": "55.27815", + "lon": "51.99763" + }, + { + "name": "улица Гагарина", + "names": { + "tt": "Гагарин урамы" + }, + "lat": "55.27597", + "lon": "51.99720" + }, + { + "name": "улица Надежды", + "lat": "55.27431", + "lon": "51.99685" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Яготин", + "enName": "Yahotyn", + "names": { + "de": "Jahotyn", + "ru": "Яготин", + "uk": "Яготин", + "prefix": "місто", + "pl": "Jagodzin" + }, + "lat": "50.27589", + "lon": "31.76349", + "id": 337525267, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Немішаєве", + "enName": "Nemishaieve", + "names": { + "ru": "Немешаево", + "uk": "Немішаєве", + "prefix": "селище міського типу", + "fr": "Nemishayeve" + }, + "lat": "50.56801", + "lon": "30.10151", + "id": 337519630, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Кагарлик", + "enName": "Kaharlyk", + "names": { + "de": "Kaharlyk", + "ru": "Кагарлык", + "uk": "Кагарлик", + "prefix": "місто", + "pl": "Kaharłyk" + }, + "lat": "49.86507", + "lon": "30.82268", + "id": 1819209275, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Буденовец", + "lat": "55.35224", + "lon": "51.14801", + "id": 19254633849, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.35043", + "lon": "51.15277", + "id": 10672, + "intersectedStreets": [ + { + "name": "улица Кирова", + "lat": "55.35478", + "lon": "51.15217" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Маметьево", + "names": { + "tt": "Мəмəт" + }, + "lat": "54.81426", + "lon": "52.08193", + "id": 867306598, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.81923", + "lon": "52.08590", + "id": 6914, + "intersectedStreets": [ + { + "name": "улица Шайдулина", + "lat": "54.81661", + "lon": "52.08882" + }, + { + "name": "Советская улица", + "lat": "54.81721", + "lon": "52.08775" + }, + { + "name": "улица Октября", + "lat": "54.81923", + "lon": "52.08590" + }, + { + "name": "улица Калинина", + "lat": "54.81973", + "lon": "52.08423" + }, + { + "name": "улица Куйбышева", + "lat": "54.81974", + "lon": "52.08234" + }, + { + "name": "улица Мириханова", + "lat": "54.82012", + "lon": "52.07936" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Шимкусы", + "lat": "55.54700", + "lon": "47.95491", + "id": 4058603, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "55.54616", + "lon": "47.94449", + "id": 11767 + } + ], + "matchStreet": 1 + }, + { + "name": "Муслюмово", + "enName": "Muslyumovo", + "names": { + "tt": "Мөслим", + "ru": "Муслюмово", + "ja": "ムスリュモヴォ" + }, + "lat": "55.30593", + "lon": "53.19415", + "id": 337839100, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Кудаш", + "names": { + "tt": "Кодаш" + }, + "lat": "56.13616", + "lon": "49.01430", + "id": 825450987, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "56.13677", + "lon": "49.01705", + "id": 8539 + } + ], + "matchStreet": 1 + }, + { + "name": "Менделеевск", + "enName": "Mendeleyevsk", + "names": { + "tt": "Менделеевск", + "de": "Mendelejewsk", + "ru": "Менделеевск", + "ja": "メンデリェーエフスク" + }, + "lat": "55.88787", + "lon": "52.30844", + "id": 321322750, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.91167", + "lon": "52.29029", + "id": 9263 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Нижний Услон", + "enName": "Nignij Uslon", + "names": { + "tt": "Түбән Ослан", + "ru": "Нижний Услон" + }, + "lat": "55.69507", + "lon": "48.96694", + "id": 703197619, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы" + }, + "lat": "55.70412", + "lon": "48.97227", + "id": 12012 + } + ], + "matchStreet": 1 + }, + { + "name": "Верхний Услон", + "enName": "Verhnij Uslon", + "names": { + "tt": "Югары Ослан", + "de": "Werchni Uslonn", + "ru": "Верхний Услон", + "ja": "ヴェルフニー・ウスロン", + "ba": "Үрге Ослан" + }, + "lat": "55.76855", + "lon": "48.98293", + "id": 703197622, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.77954", + "lon": "48.97984", + "id": 6158 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Зеленодольск", + "enName": "Zelenodol'sk", + "names": { + "tt": "Яшел Үзән", + "de": "Selenodolsk", + "ru": "Зеленодольск", + "ko": "젤레노돌스크", + "uk": "Зеленодольськ", + "mhr": "Парат", + "ja": "ゼレノドリスク", + "eo": "Zelenodolsk", + "fa": "زلنودولسک، روسیه", + "pl": "Zielonodolsk", + "ro": "Zelenodolsk", + "sr": "Зеленодољск" + }, + "lat": "55.84776", + "lon": "48.50970", + "id": 336524051, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.84548", + "lon": "48.51730", + "id": 504 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Вятские Поляны", + "enName": "Vyatskiye Polyany", + "names": { + "tt": "Нократ Аланы", + "de": "Wjatskije Poljany", + "ja": "ヴャーツキエ・ポリャーヌィ" + }, + "lat": "56.23101", + "lon": "51.02939", + "id": 3237869, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.21668", + "lon": "51.03392", + "id": 9799 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Чирша", + "names": { + "tt": "Чыршы" + }, + "lat": "56.08640", + "lon": "49.22040", + "id": 816440693, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Ленина", + "lat": "56.08601", + "lon": "49.18292", + "id": 867 + } + ], + "matchStreet": 1 + }, + { + "name": "Глеваха", + "enName": "Hlevakha", + "names": { + "kk": "Glevaha", + "de": "Hlewacha", + "ceb": "Hlevakha", + "be": "Глэваха", + "ru": "Глеваха", + "prefix": "селище міського типу", + "fr": "Hlevakha", + "cs": "Hlevacha", + "ar": "هليفاخا", + "hy": "Հլեվահա", + "uk": "Глеваха", + "pl": "Hłewacha", + "tr": "Glevaha", + "rmy": "Hlevaha" + }, + "lat": "50.25968", + "lon": "30.30591", + "id": 337525824, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Казань", + "enName": "Kazan", + "names": { + "tt": "Казан", + "de": "Kasan", + "hi": "काज़ान", + "fi": "Kazan", + "ru": "Казань", + "pt": "Cazã", + "lt": "Kazanė", + "lv": "Kazaņa", + "fr": "Kazan", + "hu": "Kazany", + "hy": "Կազան", + "oc": "Kazan", + "ka": "ყაზანი", + "uk": "Казань", + "sk": "Kazaň", + "sr": "Казањ", + "kn": "ಕಾಜಾ಼ನ್", + "os": "Хъазан", + "chm": "Озаҥ", + "it": "Kazan'", + "kv": "Казан", + "es": "Kazán", + "zh": "喀山", + "cs": "Kazaň", + "ar": "قازان", + "cv": "Хусан", + "mhr": "Озаҥ", + "ja": "カザン", + "pl": "Kazań", + "da": "Kazan", + "he": "קאזאן", + "ro": "Kazan", + "nl": "Kazan", + "ba": "Ҡаҙан", + "udm": "Кузон" + }, + "lat": "55.78235", + "lon": "49.12423", + "id": 27504067, + "type": "CITY", + "listOfStreets": [ + { + "name": "улица Ульянова-Ленина", + "enName": "Ulyanova-Lenina Street", + "names": { + "tt": "Ульянов-Ленин урамы", + "ru": "улица Ульянова-Ленина" + }, + "lat": "55.78637", + "lon": "49.13541", + "id": 1292 + }, + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.88035", + "lon": "49.15122", + "id": 3396 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Лебяжье", + "names": { + "tt": "Лебяжье" + }, + "lat": "55.32639", + "lon": "50.15632", + "id": 824088896, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "ru": "улица Ленина" + }, + "lat": "55.32475", + "lon": "50.15757", + "id": 4071 + } + ], + "matchStreet": 1 + }, + { + "name": "Красная Кадка", + "names": { + "tt": "Кызыл Чапчак" + }, + "lat": "55.41651", + "lon": "51.79221", + "id": 585272708, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.41732", + "lon": "51.79011", + "id": 9827, + "buildings": [ + { + "name": "60", + "lat": "55.41483", + "lon": "51.79412" + }, + { + "name": "66", + "lat": "55.41433", + "lon": "51.79492" + } + ], + "intersectedStreets": [ + { + "name": "«Чистополь — Нижнекамск» — Красная Кадка — Верхние Челны", + "lat": "55.41961", + "lon": "51.78045" + }, + { + "name": "Пионерская улица", + "lat": "55.41997", + "lon": "51.78163" + }, + { + "name": "Двор тыкрыгы", + "lat": "55.41844", + "lon": "51.78794" + }, + { + "name": "Садовая улица", + "lat": "55.41732", + "lon": "51.79011" + }, + { + "name": "Рамазанова улица", + "lat": "55.41596", + "lon": "51.79264" + }, + { + "name": "Кооперативная улица", + "lat": "55.41475", + "lon": "51.79464" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Володарка", + "enName": "Volodarka", + "names": { + "de": "Wolodarka", + "ru": "Володарка", + "uk": "Володарка", + "prefix": "селище міського типу", + "pl": "Wołodarka" + }, + "lat": "49.52250", + "lon": "29.92805", + "id": 337556285, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Старое Дрожжаное", + "enName": "Staroye Drozhzhanoye", + "names": { + "tt": "Иске Чүпрәле", + "ru": "Старое Дрожжаное", + "ja": "スターロエ・ドロジジャノエ" + }, + "lat": "54.72499", + "lon": "47.56454", + "id": 336525099, + "type": "TOWN", + "listOfStreets": [ + { + "name": "переулок Ленина", + "lat": "54.72970", + "lon": "47.55649", + "id": 7047 + }, + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.73008", + "lon": "47.55971", + "id": 7048 + }, + { + "name": "2-й переулок Ленина", + "lat": "54.72928", + "lon": "47.55312", + "id": 7073 + }, + { + "name": "3-й переулок Ленина", + "lat": "54.72663", + "lon": "47.54844", + "id": 7074 + }, + { + "name": "4-й переулок Ленина", + "lat": "54.72586", + "lon": "47.54608", + "id": 7075 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Ворзель", + "enName": "Vorzel", + "names": { + "ru": "Ворзель", + "uk": "Ворзель", + "prefix": "селище міського типу", + "pl": "Vorzel" + }, + "lat": "50.54573", + "lon": "30.15629", + "id": 337519932, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Джалиль", + "names": { + "tt": "Җәлил", + "ja": "ジャリリ" + }, + "lat": "55.02197", + "lon": "52.73899", + "id": 371194680, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.02426", + "lon": "52.73854", + "id": 5778, + "buildings": [ + { + "name": "1", + "lat": "55.02287", + "lon": "52.73862" + }, + { + "name": "2", + "lat": "55.02377", + "lon": "52.74038" + }, + { + "name": "3", + "lat": "55.02330", + "lon": "52.73824" + }, + { + "name": "4", + "lat": "55.02401", + "lon": "52.73993" + }, + { + "name": "5", + "lat": "55.02362", + "lon": "52.73777" + }, + { + "name": "6", + "lat": "55.02414", + "lon": "52.73914" + }, + { + "name": "7", + "lat": "55.02310", + "lon": "52.73725" + }, + { + "name": "8", + "lat": "55.02468", + "lon": "52.73993" + }, + { + "name": "9", + "lat": "55.02351", + "lon": "52.73680" + }, + { + "name": "10", + "lat": "55.02468", + "lon": "52.73867" + }, + { + "name": "11", + "lat": "55.02405", + "lon": "52.73738" + }, + { + "name": "12", + "lat": "55.02508", + "lon": "52.73822" + }, + { + "name": "13", + "lat": "55.02436", + "lon": "52.73691" + }, + { + "name": "14", + "lat": "55.02560", + "lon": "52.73749" + }, + { + "name": "15", + "lat": "55.02480", + "lon": "52.73654" + }, + { + "name": "15/2", + "lat": "55.02484", + "lon": "52.73691" + }, + { + "name": "16", + "lat": "55.02597", + "lon": "52.73830" + }, + { + "name": "17", + "lat": "55.02458", + "lon": "52.73560" + }, + { + "name": "18", + "lat": "55.02545", + "lon": "52.73948" + }, + { + "name": "19", + "lat": "55.02402", + "lon": "52.73622" + }, + { + "name": "21", + "lat": "55.02366", + "lon": "52.73502" + }, + { + "name": "23", + "lat": "55.02309", + "lon": "52.73579" + } + ], + "intersectedStreets": [ + { + "name": "улица Ветеранов", + "lat": "55.02276", + "lon": "52.74023" + }, + { + "name": "улица 30 лет Победы", + "lat": "55.02614", + "lon": "52.73637" + }, + { + "name": "улица Ахмадиева", + "lat": "55.02262", + "lon": "52.73985" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Шугурово", + "enName": "Shugurovo", + "names": { + "tt": "Шөгер", + "ja": "シュグロヴォ" + }, + "lat": "54.50877", + "lon": "52.13047", + "id": 779202585, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.50936", + "lon": "52.13549", + "id": 6724 + } + ], + "matchStreet": 1 + }, + { + "name": "Камышлы-Куль", + "names": { + "tt": "Камышлы Күл" + }, + "lat": "54.77764", + "lon": "53.07253", + "id": 587988771, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "enName": "Lenina Street", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.77790", + "lon": "53.07214", + "id": 4498 + } + ], + "matchStreet": 1 + }, + { + "name": "Городок", + "names": { + "ru": "Городок", + "uk": "Городок", + "prefix": "селище міського типу", + "pl": "Gródek" + }, + "lat": "50.58336", + "lon": "29.47816", + "id": 6726413, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Біла Церква", + "enName": "Bila Tserkva", + "names": { + "de": "Bila Zerkwa", + "be": "Белая Царква", + "ru": "Белая Церковь", + "prefix": "місто", + "lt": "Bila Cerkva", + "eo": "Bila-Cerkva", + "fr": "Bila Tserkva", + "hu": "Bila Cerkva", + "et": "Bila Tserkva", + "cs": "Bila Cerkva", + "uk": "Біла Церква", + "pl": "Biała Cerkiew", + "ro": "Bila Țerkva", + "sr": "Била Церква" + }, + "lat": "49.80382", + "lon": "30.11170", + "id": 255259998, + "type": "CITY", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Старая Майна", + "enName": "Staraya Mayna", + "names": { + "ru": "Старая Майна", + "ja": "スターラヤ・マイナ" + }, + "lat": "54.61114", + "lon": "48.92349", + "id": 11135157981, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Туба", + "names": { + "tt": "Тоба" + }, + "lat": "55.46819", + "lon": "51.74410", + "id": 585272714, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.46972", + "lon": "51.74447", + "id": 10598, + "buildings": [ + { + "name": "19", + "lat": "55.47337", + "lon": "51.74342" + } + ], + "intersectedStreets": [ + { + "name": "улица Зая", + "lat": "55.46627", + "lon": "51.74625" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Дым-Тамак", + "names": { + "tt": "Димтамак" + }, + "lat": "54.52695", + "lon": "53.35881", + "id": 566546537, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "54.52743", + "lon": "53.35989", + "id": 563 + } + ], + "matchStreet": 1 + }, + { + "name": "Исергапово", + "names": { + "tt": "Исергәп" + }, + "lat": "54.33162", + "lon": "53.38497", + "id": 750057945, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "54.33273", + "lon": "53.39147", + "id": 12219 + } + ], + "matchStreet": 1 + }, + { + "name": "Высокая Гора", + "names": { + "tt": "Биектау", + "ja": "ヴイソーカヤ・ゴラー" + }, + "lat": "55.91018", + "lon": "49.30720", + "id": 704304024, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Дубъязы", + "names": { + "tt": "Дөбьяз" + }, + "lat": "56.11974", + "lon": "49.20088", + "id": 698423501, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Ленина", + "lat": "56.12148", + "lon": "49.19356", + "id": 11793 + } + ], + "matchStreet": 1 + }, + { + "name": "Большие Яльчики", + "names": { + "cv": "Аслӑ Елчӗк", + "ru": "Большие Яльчики" + }, + "lat": "55.17187", + "lon": "48.05579", + "id": 733535693, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.16625", + "lon": "48.05624", + "id": 6274 + } + ], + "matchStreet": 1 + }, + { + "name": "Агидель", + "enName": "Agidel", + "names": { + "tt": "Агыйдел", + "ru": "Агидель", + "ja": "アギデーリ", + "ba": "Ағиҙел", + "et": "Agidel" + }, + "lat": "55.89928", + "lon": "53.93742", + "id": 251071107, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Бегишево", + "names": { + "tt": "Бигеш" + }, + "lat": "55.48721", + "lon": "52.03453", + "id": 586094745, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "55.48693", + "lon": "52.03743", + "id": 9484, + "buildings": [ + { + "name": "7А", + "lat": "55.48501", + "lon": "52.04438" + }, + { + "name": "10", + "lat": "55.48424", + "lon": "52.04204" + }, + { + "name": "28", + "lat": "55.48666", + "lon": "52.04213" + }, + { + "name": "90", + "lat": "55.49077", + "lon": "52.03061" + }, + { + "name": "99", + "lat": "55.48989", + "lon": "52.02859" + } + ], + "intersectedStreets": [ + { + "name": "Клубная улица", + "lat": "55.48871", + "lon": "52.03440" + }, + { + "name": "Молодёжная улица", + "lat": "55.48749", + "lon": "52.03649" + }, + { + "name": "улица Гагарина", + "lat": "55.49010", + "lon": "52.02825" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Большая Атня", + "enName": "Bolshaya Atnya", + "names": { + "tt": "Олы Әтнә", + "ru": "Большая Атня", + "ja": "ボリシャヤ・アトニャ" + }, + "lat": "56.24947", + "lon": "49.45390", + "id": 698459097, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Енабердино", + "names": { + "tt": "Енабердино" + }, + "lat": "55.97809", + "lon": "52.32820", + "id": 775959290, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "55.98322", + "lon": "52.33127", + "id": 12140 + } + ], + "matchStreet": 1 + }, + { + "name": "Лубяны", + "names": { + "tt": "Лубян", + "ja": "ルビャヌイ" + }, + "lat": "56.03798", + "lon": "51.39806", + "id": 797805233, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.03292", + "lon": "51.39512", + "id": 9489 + } + ], + "matchStreet": 1 + }, + { + "name": "Верхняя Ошторма", + "names": { + "tt": "Югары Оштырма" + }, + "lat": "56.08485", + "lon": "50.72480", + "id": 797012416, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы" + }, + "lat": "56.08551", + "lon": "50.72538", + "id": 12081 + } + ], + "matchStreet": 1 + }, + { + "name": "Болгар", + "enName": "Bolgar", + "names": { + "tt": "Болгар", + "cv": "Пӑлхар", + "ru": "Болгар", + "ja": "ボルガル" + }, + "lat": "54.97474", + "lon": "49.03020", + "id": 599924350, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.98034", + "lon": "49.03321", + "id": 469 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Челно-Вершины", + "lat": "54.41588", + "lon": "51.08582", + "id": 8519359, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Старые Челны", + "lat": "55.69735", + "lon": "52.30569", + "id": 3312654093, + "type": "SUBURB", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.69949", + "lon": "52.31056", + "id": 1335, + "intersectedStreets": [ + { + "name": "Полевая улица", + "lat": "55.69823", + "lon": "52.30951" + }, + { + "name": "улица Фрунзе", + "names": { + "tt": "Фрунзе урамы" + }, + "lat": "55.69823", + "lon": "52.30951" + }, + { + "name": "улица Карла Маркса", + "names": { + "tt": "Карл Маркс урамы" + }, + "lat": "55.69994", + "lon": "52.31026" + }, + { + "name": "Красногвардейская улица", + "lat": "55.70028", + "lon": "52.31007" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Бородянка", + "enName": "Borodianka", + "names": { + "de": "Borodjanka", + "ru": "Бородянка", + "uk": "Бородянка", + "prefix": "селище міського типу", + "pl": "Borodzianka" + }, + "lat": "50.64657", + "lon": "29.92558", + "id": 295809953, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Красятичі", + "enName": "Krasiatychi", + "names": { + "de": "Krasjatytschi", + "ru": "Красятичи", + "uk": "Красятичі", + "prefix": "селище міського типу", + "pl": "Krasiatyczi" + }, + "lat": "51.07829", + "lon": "29.63779", + "id": 337512030, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Черемушка", + "names": { + "tt": "Шомыртлы" + }, + "lat": "54.96081", + "lon": "50.54108", + "id": 1071321497, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы" + }, + "lat": "54.96309", + "lon": "50.54505", + "id": 11100 + } + ], + "matchStreet": 1 + }, + { + "name": "Іванків", + "enName": "Ivankiv", + "names": { + "de": "Iwankiw", + "ru": "Иванков", + "uk": "Іванків", + "prefix": "селище міського типу", + "pl": "Iwanków" + }, + "lat": "50.93300", + "lon": "29.90463", + "id": 337513819, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Березняк", + "names": { + "tt": "Березняк" + }, + "lat": "56.11196", + "lon": "50.79986", + "id": 797026502, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.10540", + "lon": "50.79771", + "id": 2940 + } + ], + "matchStreet": 1 + }, + { + "name": "Русский Акташ", + "enName": "Russkiy Aktash", + "names": { + "tt": "Рус Акташы", + "ru": "Русский Акташ", + "ja": "ルースキー・アクタシ" + }, + "lat": "55.04037", + "lon": "52.11699", + "id": 337839143, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.04846", + "lon": "52.11421", + "id": 5590, + "intersectedStreets": [ + { + "name": "Советская улица", + "lat": "55.04574", + "lon": "52.11766" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Большие Аты", + "names": { + "tt": "Олы Аты" + }, + "lat": "55.39833", + "lon": "51.74820", + "id": 585272678, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.39935", + "lon": "51.74031", + "id": 9314, + "buildings": [ + { + "name": "26", + "lat": "55.40090", + "lon": "51.74322" + }, + { + "name": "41", + "lat": "55.40116", + "lon": "51.74269" + } + ], + "intersectedStreets": [ + { + "name": "Заинск — Сухарево", + "names": { + "alt_name": "Нижнекамск — Заинск" + }, + "lat": "55.40197", + "lon": "51.74412" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Ставище", + "enName": "Stavysche", + "names": { + "de": "Stawyschtsche", + "ru": "Ставище", + "uk": "Ставище", + "prefix": "селище міського типу", + "pl": "Stawyszcze" + }, + "lat": "49.38620", + "lon": "30.18316", + "id": 337560920, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Тямле Чишма", + "names": { + "tt": "Тәмле Чишмә" + }, + "lat": "56.07587", + "lon": "50.74705", + "id": 797012425, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы" + }, + "lat": "56.07592", + "lon": "50.74641", + "id": 12080 + } + ], + "matchStreet": 1 + }, + { + "name": "Новошешминск", + "enName": "Novosheshminsk", + "names": { + "tt": "Яңа Чишмә", + "de": "Nowoscheschminsk", + "ru": "Новошешминск", + "ja": "ノヴォシェシミンスク" + }, + "lat": "55.06165", + "lon": "51.22489", + "id": 337839146, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.06093", + "lon": "51.21983", + "id": 7527, + "buildings": [ + { + "name": "6", + "lat": "55.06695", + "lon": "51.24049" + }, + { + "name": "19", + "lat": "55.06512", + "lon": "51.23646" + }, + { + "name": "25", + "lat": "55.06461", + "lon": "51.23500" + }, + { + "name": "28", + "lat": "55.06566", + "lon": "51.23423" + }, + { + "name": "39", + "lat": "55.06280", + "lon": "51.22768" + }, + { + "name": "42", + "lat": "55.06424", + "lon": "51.23133" + }, + { + "name": "48", + "lat": "55.06362", + "lon": "51.22912" + }, + { + "name": "49", + "lat": "55.06209", + "lon": "51.22509" + }, + { + "name": "53", + "lat": "55.06176", + "lon": "51.22466" + }, + { + "name": "93", + "lat": "55.05897", + "lon": "51.21352" + } + ], + "intersectedStreets": [ + { + "name": "Чистополь - Новошешминск", + "lat": "55.05613", + "lon": "51.21116" + }, + { + "name": "Полевая улица", + "lat": "55.05843", + "lon": "51.21255" + }, + { + "name": "Лесная улица", + "lat": "55.05935", + "lon": "51.21395" + }, + { + "name": "улица Мира", + "lat": "55.05980", + "lon": "51.21564" + }, + { + "name": "Молодёжная улица", + "lat": "55.06026", + "lon": "51.21736" + }, + { + "name": "улица Дружбы", + "lat": "55.06095", + "lon": "51.21983" + }, + { + "name": "улица Юности", + "lat": "55.06158", + "lon": "51.22219" + }, + { + "name": "Солнечная улица", + "lat": "55.06300", + "lon": "51.22736" + }, + { + "name": "Заливная улица", + "lat": "55.06372", + "lon": "51.23006" + }, + { + "name": "Набережная улица", + "lat": "55.06641", + "lon": "51.24369" + }, + { + "name": "Объездная улица", + "lat": "55.06641", + "lon": "51.24369" + }, + { + "name": "улица Чернышевского", + "names": { + "tt": "Чернышевский урамы" + }, + "lat": "55.06641", + "lon": "51.24369" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Починок Кучук", + "names": { + "tt": "Пүчинкә-Кучук", + "ru": "Починок Кучук", + "mhr": "Уял", + "chm": "Уял" + }, + "lat": "56.08026", + "lon": "50.87539", + "id": 797026508, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.07401", + "lon": "50.87515", + "id": 10463 + } + ], + "matchStreet": 1 + }, + { + "name": "Протопоповка", + "names": { + "tt": "Протопоповка" + }, + "lat": "55.77319", + "lon": "48.52170", + "id": 752193178, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.77312", + "lon": "48.51820", + "id": 5053 + } + ], + "matchStreet": 1 + }, + { + "name": "Билярск", + "names": { + "tt": "Биләр" + }, + "lat": "54.98370", + "lon": "50.38888", + "id": 336524609, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.98858", + "lon": "50.37849", + "id": 156 + } + ], + "matchStreet": 1 + }, + { + "name": "Верхняя Уратьма", + "names": { + "tt": "Югары Уратма" + }, + "lat": "55.25698", + "lon": "51.75228", + "id": 2523940608, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.26365", + "lon": "51.74078", + "id": 479, + "buildings": [ + { + "name": "1", + "lat": "55.25603", + "lon": "51.75028" + }, + { + "name": "3", + "lat": "55.25630", + "lon": "51.75003" + }, + { + "name": "12", + "lat": "55.25808", + "lon": "51.74936" + }, + { + "name": "17", + "lat": "55.25799", + "lon": "51.74861" + }, + { + "name": "44", + "lat": "55.26177", + "lon": "51.74588" + }, + { + "name": "55", + "lat": "55.26365", + "lon": "51.74078", + "postcode": "423568" + } + ], + "intersectedStreets": [ + { + "name": "Нагорная улица", + "lat": "55.25847", + "lon": "51.74870" + }, + { + "name": "Верхняя Уратьма — Благодатная", + "names": { + "alt_name": "Новошешминск - Заинск" + }, + "lat": "55.26590", + "lon": "51.73762" + }, + { + "name": "Заинск — Верхняя Уратьма — Шереметьевка", + "lat": "55.26287", + "lon": "51.74331" + }, + { + "name": "улица Гагарина", + "lat": "55.25618", + "lon": "51.75144" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Чувашский Чикилдым", + "names": { + "tt": "Чуаш Чагылдымы" + }, + "lat": "54.77986", + "lon": "48.41053", + "id": 787139174, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.78001", + "lon": "48.41117", + "id": 7544 + } + ], + "matchStreet": 1 + }, + { + "name": "Актюбинский", + "names": { + "tt": "Актүбә", + "ru": "Актюбинский", + "ja": "アクチュビンスキー" + }, + "lat": "54.81319", + "lon": "52.80257", + "id": 445771973, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.81508", + "lon": "52.80750", + "id": 4528, + "buildings": [ + { + "name": "4", + "lat": "54.81439", + "lon": "52.80778" + }, + { + "name": "6", + "lat": "54.81412", + "lon": "52.80795" + }, + { + "name": "6А", + "lat": "54.81413", + "lon": "52.80843" + }, + { + "name": "6Б", + "lat": "54.81412", + "lon": "52.80896" + }, + { + "name": "7", + "lat": "54.81365", + "lon": "52.80707" + }, + { + "name": "8", + "lat": "54.81366", + "lon": "52.80806" + }, + { + "name": "8А", + "lat": "54.81373", + "lon": "52.80909" + }, + { + "name": "9", + "lat": "54.81320", + "lon": "52.80622" + }, + { + "name": "10", + "lat": "54.81326", + "lon": "52.80806" + }, + { + "name": "12", + "lat": "54.81299", + "lon": "52.80778" + } + ], + "intersectedStreets": [ + { + "name": "Лесная улица", + "lat": "54.81508", + "lon": "52.80750" + }, + { + "name": "улица Губкина", + "lat": "54.81237", + "lon": "52.80748" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Емелькино", + "names": { + "tt": "Емелькино", + "cv": "Емелккел (Кивӗ Этемшӳ)" + }, + "lat": "54.96785", + "lon": "50.55872", + "id": 854660709, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы" + }, + "lat": "54.96861", + "lon": "50.55687", + "id": 11986 + } + ], + "matchStreet": 1 + }, + { + "name": "Яльчики", + "names": { + "de": "Jaltschiki", + "cv": "Елчӗк", + "ru": "Яльчики", + "ja": "ヤリチキ" + }, + "lat": "55.16287", + "lon": "48.00334", + "id": 850408359, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Азнакаево", + "enName": "Aznakayevo", + "names": { + "tt": "Азнакай", + "de": "Asnakajewo", + "cv": "Аснакай", + "ru": "Азнакаево", + "ja": "アズナカエヴォ" + }, + "lat": "54.86296", + "lon": "53.07922", + "id": 337839164, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "enName": "Lenina Street", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.86173", + "lon": "53.08068", + "id": 1562 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Карабаш", + "enName": "Karabash", + "names": { + "tt": "Карабаш", + "ja": "カラバシ" + }, + "lat": "54.69440", + "lon": "52.58739", + "id": 2406531050, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.69158", + "lon": "52.58031", + "id": 6794, + "intersectedStreets": [ + { + "name": "улица Аклима Мухаметзянова", + "lat": "54.69106", + "lon": "52.57904" + }, + { + "name": "улица Тукая", + "lat": "54.69313", + "lon": "52.58501" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Переяслав-Хмельницький", + "enName": "Pereiaslav-Khmelnytskyi", + "names": { + "de": "Perejaslaw-Chmelnyzkyj", + "be": "Пераяслаў-Хмяльніцкі", + "ru": "Переяслав-Хмельницкий", + "uk": "Переяслав-Хмельницький", + "prefix": "місто", + "eo": "Perejaslav-Ĥmelnickij", + "pl": "Perejasław Chmielnicki" + }, + "lat": "50.06440", + "lon": "31.44474", + "id": 337535557, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Нижнекамск", + "enName": "Nizhnekamsk", + "names": { + "cs": "Nižněkamsk", + "tt": "Түбән Кама", + "de": "Nischnekamsk", + "ru": "Нижнекамск", + "uk": "Нижньокамськ", + "ja": "ニジネカムスク", + "lt": "Nižnekamskas", + "fr": "Nijnekamsk", + "pl": "Niżniekamsk", + "es": "Nizhnekamsk" + }, + "lat": "55.64129", + "lon": "51.81603", + "id": 553037933, + "type": "CITY", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Красная Горка", + "names": { + "tt": "Красная Горка", + "ja": "クラスナヤ・ゴールカ" + }, + "lat": "55.70776", + "lon": "51.36656", + "id": 798213322, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.70766", + "lon": "51.36832", + "id": 8924, + "buildings": [ + { + "name": "5", + "lat": "55.70650", + "lon": "51.36649" + }, + { + "name": "7", + "lat": "55.70674", + "lon": "51.36698" + }, + { + "name": "9", + "lat": "55.70686", + "lon": "51.36716" + }, + { + "name": "11", + "lat": "55.70714", + "lon": "51.36769" + }, + { + "name": "13", + "lat": "55.70747", + "lon": "51.36834" + }, + { + "name": "13А", + "lat": "55.70726", + "lon": "51.36795" + }, + { + "name": "15", + "lat": "55.70882", + "lon": "51.37078" + }, + { + "name": "17", + "lat": "55.70899", + "lon": "51.37113" + }, + { + "name": "18", + "lat": "55.70925", + "lon": "51.37061" + }, + { + "name": "19", + "lat": "55.70929", + "lon": "51.37173" + }, + { + "name": "20", + "lat": "55.70936", + "lon": "51.37098" + }, + { + "name": "21", + "lat": "55.70946", + "lon": "51.37207" + }, + { + "name": "22", + "lat": "55.70946", + "lon": "51.37132" + }, + { + "name": "23", + "lat": "55.70967", + "lon": "51.37269" + }, + { + "name": "24", + "lat": "55.70967", + "lon": "51.37177" + }, + { + "name": "25", + "lat": "55.70979", + "lon": "51.37291" + }, + { + "name": "26", + "lat": "55.70983", + "lon": "51.37209" + }, + { + "name": "27", + "lat": "55.70989", + "lon": "51.37321" + }, + { + "name": "28", + "lat": "55.70996", + "lon": "51.37244" + }, + { + "name": "29", + "lat": "55.71009", + "lon": "51.37370" + }, + { + "name": "31", + "lat": "55.71021", + "lon": "51.37400" + }, + { + "name": "32", + "lat": "55.71049", + "lon": "51.37349" + }, + { + "name": "33", + "lat": "55.71038", + "lon": "51.37434" + }, + { + "name": "35", + "lat": "55.71053", + "lon": "51.37456" + }, + { + "name": "36", + "lat": "55.71061", + "lon": "51.37465" + }, + { + "name": "37", + "lat": "55.71076", + "lon": "51.37480" + }, + { + "name": "38", + "lat": "55.71087", + "lon": "51.37497" + }, + { + "name": "39", + "lat": "55.71096", + "lon": "51.37510" + }, + { + "name": "40", + "lat": "55.71106", + "lon": "51.37522" + } + ], + "intersectedStreets": [ + { + "name": "переулок Жданова", + "lat": "55.70766", + "lon": "51.36832" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Карай", + "lat": "56.04267", + "lon": "48.64798", + "id": 660900089, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.04220", + "lon": "48.65244", + "id": 11363 + } + ], + "matchStreet": 1 + }, + { + "name": "Старый Чувашский Адам", + "names": { + "tt": "Иске Чуаш Әдәмсуы" + }, + "lat": "54.94203", + "lon": "50.54104", + "id": 854660718, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "54.94502", + "lon": "50.54084", + "id": 10677 + } + ], + "matchStreet": 1 + }, + { + "name": "Малая Пурга", + "enName": "Malaya Purga", + "names": { + "ja": "マラヤ・プルガ", + "udm": "Пичи Пурга" + }, + "lat": "56.54955", + "lon": "53.00551", + "id": 621090917, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Шемурша", + "names": { + "de": "Schemurscha", + "cv": "Шӑмӑршӑ", + "ja": "シェムルシャー" + }, + "lat": "54.88548", + "lon": "47.51651", + "id": 855192927, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "54.88690", + "lon": "47.52984", + "id": 4027 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Агрыз", + "enName": "Agryz", + "names": { + "tt": "Әгерҗе", + "ru": "Агрыз", + "ja": "アグルイズ" + }, + "lat": "56.52546", + "lon": "52.99725", + "id": 1773881620, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.51014", + "lon": "52.99230", + "id": 7268 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Лениногорск", + "enName": "Leninogorsk", + "names": { + "tt": "Лениногорск", + "de": "Leninogorsk", + "cv": "Ҫӗнӗ Пӗҫмен", + "ru": "Лениногорск", + "ja": "レニノゴルスク" + }, + "lat": "54.59943", + "lon": "52.44691", + "id": 273310116, + "type": "TOWN", + "listOfStreets": [ + { + "name": "проспект Ленина", + "lat": "54.60202", + "lon": "52.44837", + "id": 787 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Кичкеево", + "names": { + "cv": "Киччӗ", + "ru": "Кичкеево" + }, + "lat": "55.46093", + "lon": "47.90395", + "id": 1297116347, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.45797", + "lon": "47.90161", + "id": 8973 + } + ], + "matchStreet": 1 + }, + { + "name": "Аксубаево", + "enName": "Aksubayevo", + "names": { + "tt": "Аксубай", + "de": "Aksubajewo", + "cv": "Аксу", + "ru": "Аксубаево", + "ja": "アクスバエヴォ" + }, + "lat": "54.84635", + "lon": "50.80554", + "id": 336524899, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "cv": "Ленин", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.84658", + "lon": "50.80338", + "id": 7551 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Нурлат", + "enName": "Nurlat", + "names": { + "tt": "Нурлат", + "de": "Nurlat", + "cv": "Нурлат", + "ru": "Нурлат", + "ja": "ヌルラート" + }, + "lat": "54.42973", + "lon": "50.80196", + "id": 336525410, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.42106", + "lon": "50.82743", + "id": 1727 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Пісківка", + "enName": "Piskivka", + "names": { + "ru": "Песковка", + "uk": "Пісківка", + "prefix": "селище міського типу", + "pl": "Piskivka" + }, + "lat": "50.69686", + "lon": "29.59311", + "id": 337517677, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Верхние Тимерсяны", + "enName": "Verkhniye Timersyany", + "names": { + "cv": "Ирҫел", + "ru": "Верхние Тимерсяны" + }, + "lat": "54.54342", + "lon": "47.65577", + "id": 336525181, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "54.53880", + "lon": "47.65507", + "id": 8893 + } + ], + "matchStreet": 1 + }, + { + "name": "Октябрьский", + "enName": "Oktjabrski", + "names": { + "tt": "Октябрьски", + "de": "Oktjabrski", + "ru": "Октябрьский", + "ja": "オクチャブリスキー", + "lt": "Oktiabrskis", + "ba": "Октябрьский", + "et": "Oktjabrski" + }, + "lat": "54.48097", + "lon": "53.46600", + "id": 191901380, + "type": "TOWN", + "listOfStreets": [ + { + "name": "проспект Ленина", + "lat": "54.48212", + "lon": "53.46630", + "id": 107 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Куйбышевский Затон", + "names": { + "tt": "Куйбышев Затоны", + "ja": "クイビシェフスキー・ザトン" + }, + "lat": "55.15645", + "lon": "49.16940", + "id": 697269501, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.15673", + "lon": "49.17463", + "id": 10637 + } + ], + "matchStreet": 1 + }, + { + "name": "Буча", + "enName": "Bucha", + "names": { + "de": "Butscha", + "ru": "Буча", + "prefix": "місто", + "uk": "Буча", + "fr": "Boutcha", + "pl": "Bucza" + }, + "lat": "50.54858", + "lon": "30.22071", + "id": 312987923, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Имянлебаш", + "names": { + "tt": "Имәнлебаш", + "ru": "Имянлебаш" + }, + "lat": "55.34954", + "lon": "51.82646", + "id": 585272661, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.34489", + "lon": "51.81624", + "id": 9304 + } + ], + "matchStreet": 1 + }, + { + "name": "Шингальчи", + "names": { + "tt": "Шәңгәлче" + }, + "lat": "55.51315", + "lon": "51.82206", + "id": 705268096, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.51207", + "lon": "51.81751", + "id": 9819, + "intersectedStreets": [ + { + "name": "улица Механизаторов", + "lat": "55.51141", + "lon": "51.81652" + }, + { + "name": "улица Гагарина", + "lat": "55.51520", + "lon": "51.82588" + }, + { + "name": "Новая улица", + "lat": "55.51673", + "lon": "51.82717" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Базарные Матаки", + "enName": "Bazarnyye Mataki", + "names": { + "tt": "Базарлы Матак", + "ja": "バザールヌイエ・マターキ" + }, + "lat": "54.90349", + "lon": "49.92754", + "id": 336524666, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.90183", + "lon": "49.92110", + "id": 2732 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Борова", + "enName": "Borova", + "names": { + "ru": "Боровая", + "uk": "Борова", + "prefix": "селище міського типу", + "pl": "Borowa" + }, + "lat": "50.17834", + "lon": "30.10254", + "id": 337527400, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Аксарино", + "names": { + "tt": "Аксар" + }, + "lat": "55.33954", + "lon": "51.91351", + "id": 585272665, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.34291", + "lon": "51.90720", + "id": 514, + "buildings": [ + { + "name": "1", + "lat": "55.34168", + "lon": "51.91549", + "postcode": "423501" + }, + { + "name": "2", + "lat": "55.34207", + "lon": "51.91523", + "postcode": "423501" + }, + { + "name": "29", + "lat": "55.34157", + "lon": "51.91092", + "postcode": "423501" + }, + { + "name": "36", + "lat": "55.34223", + "lon": "51.91001" + }, + { + "name": "38", + "lat": "55.34237", + "lon": "51.90939", + "postcode": "423501" + }, + { + "name": "40", + "lat": "55.34250", + "lon": "51.90858", + "postcode": "423501" + }, + { + "name": "40", + "lat": "55.34250", + "lon": "51.90858", + "postcode": "423501" + }, + { + "name": "41", + "lat": "55.34228", + "lon": "51.90783", + "postcode": "423501" + }, + { + "name": "42", + "lat": "55.34291", + "lon": "51.90720", + "postcode": "423501" + }, + { + "name": "42", + "lat": "55.34294", + "lon": "51.90720", + "postcode": "423501" + }, + { + "name": "46", + "lat": "55.34318", + "lon": "51.90613", + "postcode": "423501" + } + ], + "intersectedStreets": [ + { + "name": "Молодёжная улица", + "lat": "55.34181", + "lon": "51.91592" + }, + { + "name": "Колхозная улица", + "lat": "55.34197", + "lon": "51.91119" + }, + { + "name": "улица Вахитова", + "lat": "55.34261", + "lon": "51.90737" + }, + { + "name": "улица Тукая", + "lat": "55.34261", + "lon": "51.90737" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Кармалы", + "names": { + "tt": "Кармалы" + }, + "lat": "55.30420", + "lon": "51.17732", + "id": 779172045, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.30562", + "lon": "51.18112", + "id": 10574 + } + ], + "matchStreet": 1 + }, + { + "name": "Нижняя Мактама", + "names": { + "tt": "Түбəн Мактама", + "ja": "ニジニャヤ・マクタマ" + }, + "lat": "54.86247", + "lon": "52.42313", + "id": 1804405492, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Велика Димерка", + "enName": "Velyka Dymerka", + "names": { + "ru": "Великая Дымерка", + "uk": "Велика Димерка", + "prefix": "селище міського типу", + "pl": "Wielka Dymirka" + }, + "lat": "50.59183", + "lon": "30.90109", + "id": 337519261, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Вишгород", + "enName": "Vyshhorod", + "names": { + "de": "Wyschhorod", + "ru": "Вышгород", + "uk": "Вишгород", + "prefix": "місто", + "fr": "Vychhorod", + "pl": "Wyszogród" + }, + "lat": "50.58243", + "lon": "30.48513", + "id": 289308798, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Старый Токмак", + "names": { + "tt": "Иске Тәкмәк" + }, + "lat": "55.39208", + "lon": "51.98147", + "id": 586094637, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "55.39715", + "lon": "51.99192", + "id": 2837, + "intersectedStreets": [ + { + "name": "улица Речная", + "lat": "55.39594", + "lon": "51.98954" + }, + { + "name": "улица Луговая", + "lat": "55.39715", + "lon": "51.99192" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Орловка", + "enName": "Orlovka", + "names": { + "ru": "Орловка" + }, + "lat": "55.71491", + "lon": "52.37174", + "id": 568525857, + "type": "SUBURB", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "55.71641", + "lon": "52.38245", + "id": 5664, + "buildings": [ + { + "name": "9", + "lat": "55.71641", + "lon": "52.38245" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Мунайка", + "names": { + "tt": "Монай" + }, + "lat": "55.89279", + "lon": "52.26012", + "id": 775959237, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.89364", + "lon": "52.26132", + "id": 4125 + } + ], + "matchStreet": 1 + }, + { + "name": "Обухів", + "enName": "Obukhiv", + "names": { + "de": "Obuchiw", + "ru": "Обухов", + "uk": "Обухів", + "prefix": "місто", + "fr": "Oboukhiv", + "pl": "Obuchów", + "hu": "Obukhiv" + }, + "lat": "50.11020", + "lon": "30.62675", + "id": 337534108, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Черемшан", + "enName": "Cheremshan", + "names": { + "tt": "Чирмешән", + "cv": "Ҫарӑмсан", + "ru": "Черемшан", + "ja": "チェレムシャン" + }, + "lat": "54.66110", + "lon": "51.50532", + "id": 337839206, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Ядыгерь", + "names": { + "tt": "Ядегәр" + }, + "lat": "56.23022", + "lon": "50.50930", + "id": 796777404, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.22996", + "lon": "50.51228", + "id": 11226 + } + ], + "matchStreet": 1 + }, + { + "name": "Українка", + "enName": "Ukrainka", + "names": { + "ru": "Украинка", + "uk": "Українка", + "prefix": "місто", + "pl": "Ukrajinka" + }, + "lat": "50.15372", + "lon": "30.74206", + "id": 337528474, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Подгорный Дрюш", + "names": { + "tt": "Таулы Дөреш" + }, + "lat": "55.50122", + "lon": "52.55138", + "id": 594677179, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "ru": "улица Ленина" + }, + "lat": "55.50009", + "lon": "52.55241", + "id": 9785 + } + ], + "matchStreet": 1 + }, + { + "name": "Калинівка", + "enName": "Kalynivka", + "names": { + "kk": "Kalyni'vka", + "de": "Kalyniwka (Wassylkiw)", + "hy": "Կալյնիվկա", + "ru": "Калиновка", + "vi": "Kalynivka", + "prefix": "селище міського типу", + "uk": "Калинівка", + "ja": "カルィーニウカ", + "fr": "Kalynivka", + "pl": "Kalinówka", + "ro": "Kalînivka, Vasîlkiv", + "tr": "Kalınivka" + }, + "lat": "50.22573", + "lon": "30.22618", + "id": 337526421, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Фастів", + "enName": "Fastiv", + "names": { + "de": "Fastiw", + "ru": "Фастов", + "uk": "Фастів", + "prefix": "місто", + "eo": "Fastiv", + "fr": "Fastiv", + "pl": "Fastów", + "hu": "Fastiv" + }, + "lat": "50.07993", + "lon": "29.91629", + "id": 337535126, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Тамаево", + "names": { + "tt": "Тәмәй" + }, + "lat": "56.35173", + "lon": "50.83694", + "id": 797083146, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.35513", + "lon": "50.83874", + "id": 10747 + } + ], + "matchStreet": 1 + }, + { + "name": "Уруссу", + "enName": "Urussu", + "names": { + "tt": "Урыссу", + "ru": "Уруссу", + "ja": "ウルスー" + }, + "lat": "54.59933", + "lon": "53.46632", + "id": 337839216, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "enName": "Lenina Street", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.59502", + "lon": "53.46949", + "id": 2587 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Туембаш", + "names": { + "tt": "Туембаш" + }, + "lat": "56.26669", + "lon": "50.86387", + "id": 797083152, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.26702", + "lon": "50.86209", + "id": 11179 + } + ], + "matchStreet": 1 + }, + { + "name": "Елабуга", + "enName": "Elabuga", + "names": { + "tt": "Алабуга", + "ar": "ييلابوغا", + "de": "Jelabuga", + "cv": "Алапӳ", + "ru": "Елабуга", + "uk": "Єлабуга", + "ja": "エラブガ", + "tt-lat": "Alabuğa", + "fa": "یلابوگا", + "tr": "Alabuga", + "ba": "Алабуға" + }, + "lat": "55.75771", + "lon": "52.05400", + "id": 153568874, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Ютаза", + "enName": "Yutaza", + "names": { + "tt": "Ютазы", + "ru": "Ютаза" + }, + "lat": "54.58879", + "lon": "53.27073", + "id": 337839220, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "enName": "Lenina Street", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.58895", + "lon": "53.26753", + "id": 2688 + } + ], + "matchStreet": 1 + }, + { + "name": "Петровский Завод", + "enName": "Petrovsky Zavod", + "names": { + "tt": "Петровский Завод" + }, + "lat": "55.31827", + "lon": "52.49617", + "id": 582997231, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.31683", + "lon": "52.49512", + "id": 10260, + "buildings": [ + { + "name": "1", + "lat": "55.31623", + "lon": "52.49484", + "postcode": "423364" + }, + { + "name": "2", + "lat": "55.31657", + "lon": "52.49416", + "postcode": "423364" + }, + { + "name": "3", + "lat": "55.31661", + "lon": "52.49512", + "postcode": "423364" + }, + { + "name": "4", + "lat": "55.31719", + "lon": "52.49471", + "postcode": "423364" + }, + { + "name": "5", + "lat": "55.31701", + "lon": "52.49544", + "postcode": "423364" + }, + { + "name": "6", + "lat": "55.31691", + "lon": "52.49416", + "postcode": "423364" + } + ], + "intersectedStreets": [ + { + "name": "Заводская улица", + "lat": "55.31604", + "lon": "52.49452" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Макарів", + "enName": "Makariv", + "names": { + "de": "Makariw", + "ru": "Макаров", + "uk": "Макарів", + "prefix": "селище міського типу", + "pl": "Makarow" + }, + "lat": "50.46399", + "lon": "29.80690", + "id": 1494035871, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Комаровка", + "names": { + "tt": "Комаровка" + }, + "lat": "55.89661", + "lon": "50.26097", + "id": 801827934, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.89504", + "lon": "50.25857", + "id": 10199 + } + ], + "matchStreet": 1 + }, + { + "name": "Кукмор", + "enName": "Kukmor", + "names": { + "tt": "Кукмара", + "de": "Kukmor", + "ru": "Кукмор", + "ja": "クークモル" + }, + "lat": "56.18653", + "lon": "50.89721", + "id": 409487025, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.18256", + "lon": "50.90891", + "id": 748 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Київ", + "enName": "Kyiv", + "names": { + "hi": "कीव", + "pt": "Kiev", + "prefix": "місто", + "hr": "Kijev", + "ht": "Kyèv", + "hu": "Kijev", + "lmo": "Kiev", + "xmf": "კიევი", + "yi": "קיעוו", + "hy": "Կիև", + "bar": "Kiew", + "nah": "Kiev", + "yo": "Kiev", + "pms": "Kijv", + "ia": "Kyiv", + "nan": "Kyyiv", + "id": "Kyiv", + "ie": "Kyiv", + "sco": "Kiev", + "scn": "Kiev", + "ext": "Kyiv", + "ab": "Кыив", + "qu": "Kiyiw", + "af": "Kiëf", + "pnb": "کیف", + "io": "Kyiv", + "frr": "Kyiv", + "is": "Kænugarður", + "it": "Kyiv", + "am": "ኪየቭ", + "an": "Kiev", + "zh": "基輔", + "ar": "كييف", + "jbo": "kiev", + "mhr": "Киев", + "ja": "キエフ", + "az": "Kiyev", + "zu": "IKiyevi", + "ro": "Kiev", + "ba": "Киев", + "be": "Кіеў", + "ru": "Киев", + "bg": "Киев", + "bi": "Kyiv", + "myv": "Киев ош", + "bn": "কিয়েভ", + "jv": "Kyiv", + "bo": "ཀིབ།", + "br": "Kyiv", + "sc": "Kiev", + "bs": "Kijev", + "se": "Kiova", + "sh": "Kijev", + "ka": "კიევი", + "sk": "Kyjev", + "roa-rup": "Kiev", + "sl": "Kijev", + "ca": "Kyiv", + "sq": "Kievi", + "sr": "Кијев", + "kk": "Киев", + "kl": "Kyiv", + "kn": "ಕೀವ್", + "sv": "Kiev", + "ko": "키예프", + "mrj": "Киев", + "sw": "Kiev", + "arz": "كييف", + "ku": "Kîev", + "kv": "Киев", + "ta": "கீவ்", + "ky": "Киев", + "cs": "Kyjev", + "te": "క్యివ్", + "cu": "Кꙑѥвъ", + "cv": "Кийӳ", + "tg": "Киев", + "th": "เคียฟ", + "la": "Kiovia", + "cy": "Kyiv", + "lb": "Kiew", + "tl": "Kiev", + "nds": "Kiew", + "da": "Kyiv", + "tr": "Kiev", + "tt": "Киев", + "be-tarask": "Кіеў", + "de": "Kiew", + "ln": "Kyjiw", + "ast": "Kiev", + "rue": "Київ", + "tw": "Kiev", + "hif": "Kiev", + "koi": "Киев", + "lt": "Kijevas", + "lv": "Kijeva", + "lij": "Kiev", + "lad": "Kyiv", + "ug": "كىيېۋ", + "roa-tara": "Kiev", + "vec": "Kiev", + "uk": "Київ", + "fiu-vro": "Kiiova", + "mi": "Kieu", + "ur": "کیف", + "mk": "Киев", + "pap": "Kiev", + "ml": "കീവ്", + "rmy": "Kiev", + "vep": "Kijev", + "mn": "Киев", + "mr": "क्यीव", + "uz": "Kiyev", + "ms": "Kiev", + "el": "Κίεβο", + "mt": "Kjiv", + "tzl": "Kíiv", + "als": "Kiew", + "eo": "Kievo", + "my": "ကီးယက်မြို့", + "ilo": "Kyiv", + "es": "Kyiv", + "mdf": "Киев", + "et": "Kiiev", + "eu": "Kyiv", + "dsb": "Kijew", + "vi": "Kiev", + "bat-smg": "Kijevs", + "hsb": "Kijew", + "vo": "Küyiv", + "fa": "کی‌یف", + "nl": "Kiev", + "udm": "Киев", + "nn": "Kiev", + "no": "Kiev", + "fi": "Kiova", + "yue": "基輔", + "fo": "Kyiv", + "fr": "Kyiv", + "gag": "Kyiv", + "fy": "Kiev", + "nov": "Kiyev", + "oc": "Kyiiv", + "crh": "Kiyev", + "wo": "Kiyew", + "ga": "Cív", + "ang": "Cænugeard", + "sah": "Киев", + "bxr": "Киев", + "gd": "Kyiv", + "os": "Киев", + "szl": "Kijůw", + "gl": "Kiev", + "war": "Kiev", + "bpy": "কিয়েভ", + "gv": "Kyiv", + "pa": "ਕੀਵ", + "csb": "Kyiv", + "cbk-zam": "Kyiv", + "ckb": "کیێڤ", + "pl": "Kijów", + "he": "קייב" + }, + "lat": "50.45011", + "lon": "30.52405", + "id": 26150422, + "type": "CITY", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Тетіїв", + "enName": "Tetiiv", + "names": { + "de": "Tetijiw", + "ru": "Тетиев", + "uk": "Тетіїв", + "prefix": "місто", + "pl": "Tetyjów" + }, + "lat": "49.36862", + "lon": "29.67712", + "id": 337561478, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Тетюши", + "enName": "Tetyushi", + "names": { + "tt": "Тәтеш", + "de": "Tetjuschi", + "cv": "Теччӗ", + "ru": "Тетюши", + "ja": "テチューシ" + }, + "lat": "54.93431", + "lon": "48.83168", + "id": 336524694, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.93456", + "lon": "48.83687", + "id": 261 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Лаишево", + "enName": "Laishevo", + "names": { + "tt": "Лаеш", + "de": "Laischewo", + "cv": "Лаиш", + "ru": "Лаишево", + "ja": "ライシェヴォ", + "lt": "Laiševas" + }, + "lat": "55.40434", + "lon": "49.54684", + "id": 336524182, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.39917", + "lon": "49.55297", + "id": 8277 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Кошкино", + "names": { + "tt": "Күкшел" + }, + "lat": "56.33042", + "lon": "50.80158", + "id": 797079071, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.33087", + "lon": "50.80482", + "id": 11218 + } + ], + "matchStreet": 1 + }, + { + "name": "Малая Бугульма", + "names": { + "tt": "Кече Бөгелмә" + }, + "lat": "54.50524", + "lon": "52.87763", + "id": 445814288, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "54.51180", + "lon": "52.85812", + "id": 4576 + } + ], + "matchStreet": 1 + }, + { + "name": "Ржищів", + "enName": "Rzhyschiv", + "names": { + "de": "Rschyschtschiw", + "ru": "Ржищев", + "uk": "Ржищів", + "prefix": "місто", + "pl": "Rzyszczów" + }, + "lat": "49.96822", + "lon": "31.04118", + "id": 337538749, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Мамашир", + "names": { + "tt": "Мәмәшир" + }, + "lat": "56.36733", + "lon": "50.84810", + "id": 797079080, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.36595", + "lon": "50.85237", + "id": 10619 + } + ], + "matchStreet": 1 + }, + { + "name": "Миронівка", + "enName": "Myronivka", + "names": { + "de": "Myroniwka", + "ru": "Мироновка", + "uk": "Миронівка", + "prefix": "місто", + "pl": "Mironówka" + }, + "lat": "49.65834", + "lon": "30.98250", + "id": 5119801550, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Березань", + "enName": "Berezan", + "names": { + "de": "Beresan", + "ru": "Березань", + "uk": "Березань", + "prefix": "місто", + "lt": "Berezanė", + "pl": "Berezań" + }, + "lat": "50.31327", + "lon": "31.46892", + "id": 337524664, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Нижние Вязовые", + "enName": "Nizhniye Vyazovye", + "names": { + "tt": "Карамалы Тау", + "ja": "ニジニエ・ヴャゾヴイエ" + }, + "lat": "55.79963", + "lon": "48.52238", + "id": 752193024, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.81148", + "lon": "48.51114", + "id": 5094 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Осиново", + "names": { + "tt": "Осиново" + }, + "lat": "55.87730", + "lon": "48.89103", + "id": 722796219, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.87683", + "lon": "48.89710", + "id": 3975 + } + ], + "matchStreet": 1 + }, + { + "name": "Альметьевск", + "enName": "Almetyevsk", + "names": { + "tt": "Әлмәт", + "de": "Almetjewsk", + "be": "Альмецьеўск", + "ru": "Альметьевск", + "ko": "알메티옙스크", + "lt": "Almetjevskas", + "it": "Al'met'evsk", + "fr": "Almetievsk", + "es": "Almétievsk", + "zh": "阿尔梅季耶夫斯克", + "ar": "ألميتيفسك", + "cv": "Элмет", + "uk": "Альметьєвськ", + "hsb": "Almetjewsk", + "ja": "アリメチエフスク", + "az": "Əlmət", + "fa": "آلماتیفسک", + "pl": "Almietjewsk", + "ro": "Almetievsk", + "ba": "Әлмәт" + }, + "lat": "54.89965", + "lon": "52.30149", + "id": 2173235770, + "type": "CITY", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.89948", + "lon": "52.27471", + "id": 70, + "buildings": [ + { + "name": "1", + "lat": "54.90372", + "lon": "52.31715" + }, + { + "name": "1А", + "lat": "54.90288", + "lon": "52.31537" + }, + { + "name": "1А/1", + "lat": "54.90344", + "lon": "52.31443" + }, + { + "name": "1Б", + "lat": "54.90368", + "lon": "52.31606" + }, + { + "name": "1В", + "lat": "54.90384", + "lon": "52.31443" + }, + { + "name": "2", + "lat": "54.90476", + "lon": "52.31224" + }, + { + "name": "2А", + "lat": "54.90430", + "lon": "52.31679" + }, + { + "name": "2А", + "lat": "54.90432", + "lon": "52.31745" + }, + { + "name": "2А/1", + "lat": "54.90467", + "lon": "52.31466" + }, + { + "name": "3", + "lat": "54.90367", + "lon": "52.31243" + }, + { + "name": "4", + "lat": "54.90456", + "lon": "52.31011" + }, + { + "name": "5", + "lat": "54.90392", + "lon": "52.31194" + }, + { + "name": "6", + "lat": "54.90477", + "lon": "52.30921" + }, + { + "name": "7", + "lat": "54.90370", + "lon": "52.31125" + }, + { + "name": "8", + "lat": "54.90450", + "lon": "52.30904" + }, + { + "name": "10", + "lat": "54.90507", + "lon": "52.30741" + }, + { + "name": "11", + "lat": "54.90395", + "lon": "52.31001" + }, + { + "name": "12", + "lat": "54.90473", + "lon": "52.30795" + }, + { + "name": "13", + "lat": "54.90404", + "lon": "52.30872" + }, + { + "name": "14", + "lat": "54.90505", + "lon": "52.30778" + }, + { + "name": "15", + "lat": "54.90375", + "lon": "52.30795" + }, + { + "name": "15Б", + "lat": "54.90367", + "lon": "52.30904" + }, + { + "name": "16", + "lat": "54.90457", + "lon": "52.30679" + }, + { + "name": "17", + "lat": "54.90412", + "lon": "52.30726" + }, + { + "name": "18", + "lat": "54.90508", + "lon": "52.30666" + }, + { + "name": "19", + "lat": "54.90413", + "lon": "52.30606" + }, + { + "name": "20", + "lat": "54.90467", + "lon": "52.30544" + }, + { + "name": "21", + "lat": "54.90366", + "lon": "52.30445" + }, + { + "name": "21А", + "lat": "54.90400", + "lon": "52.30544" + }, + { + "name": "22", + "lat": "54.90456", + "lon": "52.30438" + }, + { + "name": "23", + "lat": "54.90340", + "lon": "52.30288" + }, + { + "name": "23", + "lat": "54.90338", + "lon": "52.30314" + }, + { + "name": "24", + "lat": "54.90416", + "lon": "52.30342" + }, + { + "name": "25", + "lat": "54.90307", + "lon": "52.30215" + }, + { + "name": "26", + "lat": "54.90384", + "lon": "52.30263" + }, + { + "name": "27", + "lat": "54.90276", + "lon": "52.30136" + }, + { + "name": "28", + "lat": "54.90350", + "lon": "52.30179" + }, + { + "name": "29", + "lat": "54.90238", + "lon": "52.30048" + }, + { + "name": "30", + "lat": "54.90354", + "lon": "52.30104" + }, + { + "name": "30А", + "lat": "54.90408", + "lon": "52.30106" + }, + { + "name": "31", + "lat": "54.90205", + "lon": "52.29984" + }, + { + "name": "32", + "lat": "54.90310", + "lon": "52.30080" + }, + { + "name": "32А", + "lat": "54.90376", + "lon": "52.30022" + }, + { + "name": "33", + "lat": "54.90183", + "lon": "52.29928" + }, + { + "name": "33А", + "lat": "54.90162", + "lon": "52.29960" + }, + { + "name": "33Б", + "lat": "54.90205", + "lon": "52.29947" + }, + { + "name": "34", + "lat": "54.90282", + "lon": "52.29992" + }, + { + "name": "35", + "lat": "54.90134", + "lon": "52.29872" + }, + { + "name": "36", + "lat": "54.90247", + "lon": "52.29908" + }, + { + "name": "37", + "names": { + "tt": "Әлмәт татар дәүләт драма театры", + "hy": "Ալմետևսկի թաթարական պետական դրամատիկական թատրոն" + }, + "lat": "54.90051", + "lon": "52.29799" + }, + { + "name": "38", + "lat": "54.90265", + "lon": "52.29818" + }, + { + "name": "39", + "lat": "54.90046", + "lon": "52.29627" + }, + { + "name": "40", + "lat": "54.90213", + "lon": "52.29829" + }, + { + "name": "41", + "lat": "54.90033", + "lon": "52.29539" + }, + { + "name": "41А", + "lat": "54.89985", + "lon": "52.29634" + }, + { + "name": "42", + "lat": "54.90188", + "lon": "52.29720" + }, + { + "name": "42А", + "lat": "54.90173", + "lon": "52.29760" + }, + { + "name": "43", + "lat": "54.90009", + "lon": "52.29477" + }, + { + "name": "44", + "lat": "54.90246", + "lon": "52.29733" + }, + { + "name": "46", + "lat": "54.90134", + "lon": "52.29619" + }, + { + "name": "47", + "lat": "54.89972", + "lon": "52.29363" + }, + { + "name": "48", + "lat": "54.90099", + "lon": "52.29529" + }, + { + "name": "49", + "lat": "54.89932", + "lon": "52.29342" + }, + { + "name": "49А", + "lat": "54.89929", + "lon": "52.29290" + }, + { + "name": "50", + "lat": "54.90071", + "lon": "52.29411" + }, + { + "name": "51", + "lat": "54.89903", + "lon": "52.29271" + }, + { + "name": "51А", + "lat": "54.89869", + "lon": "52.29363" + }, + { + "name": "52", + "lat": "54.90170", + "lon": "52.29443" + }, + { + "name": "53", + "lat": "54.89912", + "lon": "52.29209" + }, + { + "name": "55", + "lat": "54.89876", + "lon": "52.29117" + }, + { + "name": "55А", + "lat": "54.89859", + "lon": "52.29067" + }, + { + "name": "56", + "lat": "54.90022", + "lon": "52.29327" + }, + { + "name": "57", + "lat": "54.89858", + "lon": "52.29258" + }, + { + "name": "58", + "lat": "54.90020", + "lon": "52.29260" + }, + { + "name": "59", + "lat": "54.89828", + "lon": "52.29295" + }, + { + "name": "60", + "lat": "54.89986", + "lon": "52.29198", + "postcode": "423450" + }, + { + "name": "61", + "lat": "54.89797", + "lon": "52.29331" + }, + { + "name": "62", + "lat": "54.89964", + "lon": "52.29119" + }, + { + "name": "63", + "lat": "54.89765", + "lon": "52.29366" + }, + { + "name": "64", + "lat": "54.90030", + "lon": "52.29095" + }, + { + "name": "65", + "lat": "54.89740", + "lon": "52.29462" + }, + { + "name": "66", + "lat": "54.90013", + "lon": "52.29016" + }, + { + "name": "67", + "lat": "54.89718", + "lon": "52.29391" + }, + { + "name": "68", + "lat": "54.89924", + "lon": "52.29072" + }, + { + "name": "68А", + "lat": "54.89914", + "lon": "52.29082" + }, + { + "name": "69А", + "lat": "54.89788", + "lon": "52.28943" + }, + { + "name": "70", + "lat": "54.89946", + "lon": "52.28988" + }, + { + "name": "70А", + "lat": "54.89999", + "lon": "52.28924" + }, + { + "name": "71", + "lat": "54.89739", + "lon": "52.28887" + }, + { + "name": "72", + "lat": "54.89900", + "lon": "52.28975" + }, + { + "name": "72А", + "lat": "54.89877", + "lon": "52.28960" + }, + { + "name": "73", + "lat": "54.89677", + "lon": "52.28700" + }, + { + "name": "74", + "lat": "54.89909", + "lon": "52.28891" + }, + { + "name": "74А", + "lat": "54.89965", + "lon": "52.28836" + }, + { + "name": "75", + "lat": "54.89716", + "lon": "52.28722" + }, + { + "name": "75", + "lat": "54.89719", + "lon": "52.28651" + }, + { + "name": "75", + "lat": "54.89733", + "lon": "52.28683" + }, + { + "name": "75", + "lat": "54.89733", + "lon": "52.28632" + }, + { + "name": "75", + "lat": "54.89740", + "lon": "52.28673" + }, + { + "name": "75", + "lat": "54.89767", + "lon": "52.28683" + }, + { + "name": "76", + "lat": "54.89856", + "lon": "52.28900" + }, + { + "name": "77", + "lat": "54.89772", + "lon": "52.28589" + }, + { + "name": "78", + "lat": "54.89911", + "lon": "52.28795" + }, + { + "name": "80", + "lat": "54.89865", + "lon": "52.28745" + }, + { + "name": "82", + "lat": "54.89834", + "lon": "52.28795" + }, + { + "name": "82А", + "lat": "54.89842", + "lon": "52.28696" + }, + { + "name": "83", + "lat": "54.89712", + "lon": "52.28164" + }, + { + "name": "85", + "lat": "54.89790", + "lon": "52.28112" + }, + { + "name": "85", + "lat": "54.89777", + "lon": "52.28115" + }, + { + "name": "86", + "lat": "54.89866", + "lon": "52.28649" + }, + { + "name": "87", + "lat": "54.89693", + "lon": "52.28061" + }, + { + "name": "88", + "lat": "54.89838", + "lon": "52.28608" + }, + { + "name": "89", + "lat": "54.89756", + "lon": "52.27986" + }, + { + "name": "90", + "lat": "54.89865", + "lon": "52.28565" + }, + { + "name": "91", + "lat": "54.89700", + "lon": "52.27898" + }, + { + "name": "92", + "lat": "54.89923", + "lon": "52.28597" + }, + { + "name": "93", + "lat": "54.89785", + "lon": "52.27864" + }, + { + "name": "94", + "lat": "54.89999", + "lon": "52.28123" + }, + { + "name": "95", + "lat": "54.89703", + "lon": "52.27812" + }, + { + "name": "97", + "lat": "54.89764", + "lon": "52.27737" + }, + { + "name": "98", + "lat": "54.89920", + "lon": "52.27746" + }, + { + "name": "99", + "lat": "54.89708", + "lon": "52.27651" + }, + { + "name": "100", + "lat": "54.89948", + "lon": "52.27471" + }, + { + "name": "100", + "lat": "54.89941", + "lon": "52.27391" + }, + { + "name": "101", + "lat": "54.89790", + "lon": "52.27615" + }, + { + "name": "103", + "lat": "54.89713", + "lon": "52.27565" + }, + { + "name": "104А", + "lat": "54.90096", + "lon": "52.26671" + }, + { + "name": "104Б", + "lat": "54.90123", + "lon": "52.26572" + }, + { + "name": "104Б", + "lat": "54.90134", + "lon": "52.26632" + }, + { + "name": "105", + "lat": "54.89772", + "lon": "52.27490" + }, + { + "name": "106", + "lat": "54.90203", + "lon": "52.26681" + }, + { + "name": "107", + "lat": "54.89718", + "lon": "52.27404" + }, + { + "name": "108", + "lat": "54.90261", + "lon": "52.26694" + }, + { + "name": "109", + "lat": "54.89804", + "lon": "52.27370" + }, + { + "name": "110", + "lat": "54.90314", + "lon": "52.26709" + }, + { + "name": "110А", + "lat": "54.90367", + "lon": "52.26735" + }, + { + "name": "111", + "lat": "54.89718", + "lon": "52.27316" + }, + { + "name": "112", + "lat": "54.90187", + "lon": "52.26507" + }, + { + "name": "112", + "lat": "54.90208", + "lon": "52.26417" + }, + { + "name": "112А", + "lat": "54.90164", + "lon": "52.26495" + }, + { + "name": "113", + "lat": "54.89780", + "lon": "52.27243" + }, + { + "name": "113А", + "lat": "54.89817", + "lon": "52.27250" + }, + { + "name": "114А", + "lat": "54.90235", + "lon": "52.26383" + }, + { + "name": "114Б", + "lat": "54.90261", + "lon": "52.26357" + }, + { + "name": "115", + "lat": "54.89722", + "lon": "52.27153" + }, + { + "name": "115А", + "lat": "54.89707", + "lon": "52.27235" + }, + { + "name": "116", + "lat": "54.90312", + "lon": "52.26297" + }, + { + "name": "117", + "lat": "54.89809", + "lon": "52.27119" + }, + { + "name": "118", + "lat": "54.90373", + "lon": "52.26385" + }, + { + "name": "119", + "lat": "54.89750", + "lon": "52.27067" + }, + { + "name": "119А", + "lat": "54.89767", + "lon": "52.27046" + }, + { + "name": "120", + "lat": "54.90373", + "lon": "52.26222" + }, + { + "name": "121", + "lat": "54.90097", + "lon": "52.26434" + }, + { + "name": "121А", + "lat": "54.89927", + "lon": "52.26720" + }, + { + "name": "121Б", + "lat": "54.89908", + "lon": "52.26810" + }, + { + "name": "121В", + "lat": "54.89887", + "lon": "52.26666" + }, + { + "name": "122", + "lat": "54.90333", + "lon": "52.26179" + }, + { + "name": "122", + "lat": "54.90325", + "lon": "52.26166" + }, + { + "name": "123", + "lat": "54.89999", + "lon": "52.26572" + }, + { + "name": "123Б", + "lat": "54.89927", + "lon": "52.26617" + }, + { + "name": "123В", + "lat": "54.89956", + "lon": "52.26664" + }, + { + "name": "124", + "lat": "54.90527", + "lon": "52.26482" + }, + { + "name": "124 с1", + "lat": "54.90573", + "lon": "52.25909" + }, + { + "name": "124 с2", + "lat": "54.90595", + "lon": "52.25939" + }, + { + "name": "125", + "lat": "54.89923", + "lon": "52.26499" + }, + { + "name": "126", + "lat": "54.90662", + "lon": "52.25707" + }, + { + "name": "127", + "lat": "54.89859", + "lon": "52.26495" + }, + { + "name": "128", + "lat": "54.90627", + "lon": "52.25585" + }, + { + "name": "129", + "lat": "54.89812", + "lon": "52.26460" + }, + { + "name": "129А", + "lat": "54.89823", + "lon": "52.26400" + }, + { + "name": "131", + "lat": "54.89891", + "lon": "52.26377" + }, + { + "name": "132", + "lat": "54.90779", + "lon": "52.25540" + }, + { + "name": "135", + "lat": "54.90069", + "lon": "52.26252" + }, + { + "name": "137", + "lat": "54.89929", + "lon": "52.26304" + }, + { + "name": "139", + "lat": "54.89920", + "lon": "52.26183" + }, + { + "name": "139Б", + "lat": "54.89953", + "lon": "52.26179" + }, + { + "name": "140", + "lat": "54.90801", + "lon": "52.25224", + "postcode": "423455" + }, + { + "name": "140", + "lat": "54.90851", + "lon": "52.25256", + "postcode": "423455" + }, + { + "name": "141", + "lat": "54.89976", + "lon": "52.26070" + }, + { + "name": "143", + "lat": "54.90057", + "lon": "52.26031" + }, + { + "name": "145", + "lat": "54.90143", + "lon": "52.26072" + }, + { + "name": "147", + "lat": "54.90157", + "lon": "52.26005" + }, + { + "name": "149", + "lat": "54.90129", + "lon": "52.26237" + }, + { + "name": "151", + "lat": "54.90185", + "lon": "52.26243" + }, + { + "name": "153", + "lat": "54.90223", + "lon": "52.26138" + }, + { + "name": "157", + "lat": "54.90117", + "lon": "52.25885" + }, + { + "name": "157А", + "lat": "54.90057", + "lon": "52.25909" + }, + { + "name": "171", + "lat": "54.90199", + "lon": "52.25671" + }, + { + "name": "187", + "lat": "54.90302", + "lon": "52.25544" + }, + { + "name": "189", + "lat": "54.90335", + "lon": "52.25385" + }, + { + "name": "193", + "lat": "54.90488", + "lon": "52.25400" + }, + { + "name": "195", + "lat": "54.90462", + "lon": "52.25572" + }, + { + "name": "195", + "lat": "54.90418", + "lon": "52.25679" + }, + { + "name": "195", + "lat": "54.90451", + "lon": "52.25647" + }, + { + "name": "197", + "lat": "54.90426", + "lon": "52.25340" + }, + { + "name": "199", + "lat": "54.90487", + "lon": "52.25209" + }, + { + "name": "201", + "lat": "54.90619", + "lon": "52.25250" + }, + { + "name": "203", + "lat": "54.90720", + "lon": "52.25070" + } + ], + "intersectedStreets": [ + { + "name": "улица Герцена", + "names": { + "tt": "Герцен урамы" + }, + "lat": "54.90398", + "lon": "52.31797" + }, + { + "name": "улица Ризы Фахретдина", + "names": { + "tt": "Риза Фәхреддин урамы" + }, + "lat": "54.90398", + "lon": "52.31797" + }, + { + "name": "проспект Тукая", + "names": { + "tt": "Тукай проспекты", + "ru": "проспект Тукая" + }, + "lat": "54.90412", + "lon": "52.31383" + }, + { + "name": "проспект Зарипова", + "lat": "54.90766", + "lon": "52.25008" + }, + { + "name": "улица Аминова", + "lat": "54.90362", + "lon": "52.25967" + }, + { + "name": "улица Зифы Балакиной", + "lat": "54.89988", + "lon": "52.26761" + }, + { + "name": "улица Марджани", + "lat": "54.89816", + "lon": "52.28522" + }, + { + "name": "улица Чехова", + "names": { + "tt": "Чехов урамы", + "tt-lat": "Çexov uramı" + }, + "lat": "54.90434", + "lon": "52.30475" + }, + { + "name": "улица Пушкина", + "names": { + "tt": "Пушкин урамы" + }, + "lat": "54.90378", + "lon": "52.30331" + }, + { + "name": "улица Маяковского", + "names": { + "tt": "Маяковский урамы" + }, + "lat": "54.90423", + "lon": "52.31044" + }, + { + "name": "улица Островского", + "names": { + "tt": "Островский урамы" + }, + "lat": "54.90437", + "lon": "52.30589" + }, + { + "name": "улица Радищева", + "names": { + "tt": "Радищев урамы" + }, + "lat": "54.90244", + "lon": "52.29984" + }, + { + "name": "улица Гагарина", + "lat": "54.90141", + "lon": "52.29720" + }, + { + "name": "улица Джалиля", + "names": { + "tt": "Җәлил урамы" + }, + "lat": "54.90024", + "lon": "52.29413" + }, + { + "name": "улица Заслонова", + "names": { + "tt": "Заслонов урамы" + }, + "lat": "54.89871", + "lon": "52.29014" + } + ] + }, + { + "name": "Ленина", + "lat": "54.90885", + "lon": "52.25265", + "id": 726, + "buildings": [ + { + "name": "140", + "lat": "54.90885", + "lon": "52.25265" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Давликеево", + "names": { + "tt": "Дәүләки" + }, + "lat": "55.21625", + "lon": "48.36310", + "id": 703166489, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.21762", + "lon": "48.36134", + "id": 6609 + } + ], + "matchStreet": 1 + }, + { + "name": "Большая Цильна", + "names": { + "tt": "Зур Чынлы" + }, + "lat": "54.68808", + "lon": "47.85389", + "id": 762000538, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.69761", + "lon": "47.85312", + "id": 10335 + } + ], + "matchStreet": 1 + }, + { + "name": "Апастово", + "enName": "Apastovo", + "names": { + "tt": "Апас", + "ar": "أباستوفو", + "de": "Apastowo", + "cv": "Апас", + "ru": "Апастово", + "ja": "アパソトヴォ", + "ca": "Apàstovo", + "ba": "Апас" + }, + "lat": "55.20267", + "lon": "48.50687", + "id": 703166491, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "enName": "Lenina Street", + "names": { + "tt": "Ленин урамы", + "de": "uliza Lenina", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.20401", + "lon": "48.51266", + "id": 2565 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Балтаси", + "enName": "Baltasi", + "names": { + "tt": "Балтач", + "ru": "Балтаси", + "ja": "バルタシ" + }, + "lat": "56.34462", + "lon": "50.21142", + "id": 701721057, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "56.34680", + "lon": "50.20677", + "id": 275 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Светлое Озеро", + "names": { + "tt": "Яктыкүл" + }, + "lat": "55.18347", + "lon": "52.01205", + "id": 586094617, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.17911", + "lon": "52.01449", + "id": 525, + "buildings": [ + { + "name": "1", + "lat": "55.19065", + "lon": "52.00960" + }, + { + "name": "2", + "lat": "55.19073", + "lon": "52.00889" + }, + { + "name": "45", + "lat": "55.18594", + "lon": "52.01245" + }, + { + "name": "77", + "lat": "55.18224", + "lon": "52.01449" + }, + { + "name": "79", + "lat": "55.18177", + "lon": "52.01473" + }, + { + "name": "81", + "lat": "55.18094", + "lon": "52.01533", + "postcode": "423538" + }, + { + "name": "83", + "lat": "55.18046", + "lon": "52.01499" + }, + { + "name": "94", + "lat": "55.17950", + "lon": "52.01449", + "postcode": "423538" + }, + { + "name": "96", + "lat": "55.17911", + "lon": "52.01449", + "postcode": "423538" + }, + { + "name": "96", + "lat": "55.17912", + "lon": "52.01449", + "postcode": "423538" + }, + { + "name": "98", + "lat": "55.17871", + "lon": "52.01451" + } + ], + "intersectedStreets": [ + { + "name": "улица Академика Акатьева", + "lat": "55.18053", + "lon": "52.01464" + }, + { + "name": "переулок Вахитова", + "lat": "55.17898", + "lon": "52.01479" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Камское Устье", + "enName": "Kamskoye Ustye", + "names": { + "tt": "Кама Тамагы", + "cv": "Кама Вӑрри", + "ru": "Камское Устье", + "ja": "カムスコエ・ウスチエ" + }, + "lat": "55.20157", + "lon": "49.26802", + "id": 336524471, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.20416", + "lon": "49.26960", + "id": 7493 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Кагарлик", + "lat": "49.85635", + "lon": "30.74794", + "id": 6629021, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Старое Клянчино", + "names": { + "tt": "Иске Теләнче", + "ru": "Старое Клянчино" + }, + "lat": "55.47022", + "lon": "52.52591", + "id": 582997319, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "ru": "улица Ленина" + }, + "lat": "55.46914", + "lon": "52.52182", + "id": 11253 + } + ], + "matchStreet": 1 + }, + { + "name": "Заря", + "names": { + "tt": "Заря", + "ja": "ザリャー" + }, + "lat": "54.99205", + "lon": "50.84252", + "id": 854647311, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы" + }, + "lat": "54.99286", + "lon": "50.84267", + "id": 12004 + } + ], + "matchStreet": 1 + }, + { + "name": "Коцюбинське", + "enName": "Kotsiubynske", + "names": { + "ru": "Коцюбинское", + "uk": "Коцюбинське", + "prefix": "селище міського типу", + "pl": "Kotsiubynske" + }, + "lat": "50.49047", + "lon": "30.33379", + "id": 337520861, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Большое Афанасово", + "names": { + "tt": "Олы Афанас", + "ru": "Большое Афанасово" + }, + "lat": "55.59972", + "lon": "51.77507", + "id": 2463557141, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.60991", + "lon": "51.79354", + "id": 9734, + "buildings": [ + { + "name": "1", + "lat": "55.60995", + "lon": "51.79442" + }, + { + "name": "2", + "lat": "55.61010", + "lon": "51.79384" + }, + { + "name": "2А", + "lat": "55.61071", + "lon": "51.79333" + }, + { + "name": "3", + "lat": "55.60971", + "lon": "51.79416" + }, + { + "name": "4", + "lat": "55.60991", + "lon": "51.79354" + }, + { + "name": "5", + "lat": "55.60952", + "lon": "51.79393" + }, + { + "name": "6", + "lat": "55.60972", + "lon": "51.79331" + }, + { + "name": "7", + "lat": "55.60934", + "lon": "51.79361" + }, + { + "name": "8", + "lat": "55.60953", + "lon": "51.79305" + }, + { + "name": "9", + "lat": "55.60920", + "lon": "51.79339" + }, + { + "name": "10", + "lat": "55.60929", + "lon": "51.79264" + }, + { + "name": "11", + "lat": "55.60898", + "lon": "51.79307" + }, + { + "name": "12", + "lat": "55.60909", + "lon": "51.79225" + }, + { + "name": "13", + "lat": "55.60884", + "lon": "51.79283" + }, + { + "name": "14", + "lat": "55.60894", + "lon": "51.79200" + }, + { + "name": "15", + "lat": "55.60872", + "lon": "51.79266" + }, + { + "name": "16", + "lat": "55.60881", + "lon": "51.79183" + }, + { + "name": "17", + "lat": "55.60862", + "lon": "51.79247" + }, + { + "name": "18", + "lat": "55.60869", + "lon": "51.79163" + }, + { + "name": "19", + "lat": "55.60843", + "lon": "51.79217" + }, + { + "name": "20", + "lat": "55.60849", + "lon": "51.79144" + }, + { + "name": "21", + "lat": "55.60827", + "lon": "51.79189" + }, + { + "name": "22", + "lat": "55.60834", + "lon": "51.79116" + }, + { + "name": "23", + "lat": "55.60805", + "lon": "51.79155" + }, + { + "name": "24", + "lat": "55.60820", + "lon": "51.79095" + }, + { + "name": "25", + "lat": "55.60791", + "lon": "51.79133" + }, + { + "name": "26", + "lat": "55.60808", + "lon": "51.79075" + }, + { + "name": "27", + "lat": "55.60778", + "lon": "51.79114" + }, + { + "name": "27А", + "lat": "55.60766", + "lon": "51.79092" + }, + { + "name": "28", + "lat": "55.60795", + "lon": "51.79054" + }, + { + "name": "29", + "lat": "55.60752", + "lon": "51.79067" + }, + { + "name": "30", + "lat": "55.60780", + "lon": "51.79028" + }, + { + "name": "31", + "lat": "55.60735", + "lon": "51.79043" + }, + { + "name": "32", + "lat": "55.60778", + "lon": "51.78938" + }, + { + "name": "33", + "lat": "55.60718", + "lon": "51.79017" + }, + { + "name": "34", + "lat": "55.60743", + "lon": "51.78949" + }, + { + "name": "35", + "lat": "55.60703", + "lon": "51.78994" + }, + { + "name": "36", + "lat": "55.60721", + "lon": "51.78949" + }, + { + "name": "37", + "lat": "55.60686", + "lon": "51.78966" + }, + { + "name": "39", + "lat": "55.60672", + "lon": "51.78942" + }, + { + "name": "39А", + "lat": "55.60652", + "lon": "51.78884" + }, + { + "name": "41", + "lat": "55.60662", + "lon": "51.78848" + }, + { + "name": "42", + "lat": "55.60683", + "lon": "51.78846" + }, + { + "name": "42Б", + "lat": "55.60658", + "lon": "51.78747" + }, + { + "name": "45", + "lat": "55.60627", + "lon": "51.78783" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Саклов-Баш", + "names": { + "tt": "Саклаубаш" + }, + "lat": "55.37935", + "lon": "52.88140", + "id": 593610600, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "tt-lat": "Lenin uramı" + }, + "lat": "55.37916", + "lon": "52.88016", + "id": 1816 + } + ], + "matchStreet": 1 + }, + { + "name": "Рокитне", + "enName": "Rokytne", + "names": { + "de": "Rokytne", + "ru": "Ракитное", + "uk": "Рокитне", + "prefix": "селище міського типу", + "pl": "Rokitna" + }, + "lat": "49.68673", + "lon": "30.47303", + "id": 337549274, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Богуслав", + "enName": "Bohuslav", + "names": { + "de": "Bohuslaw", + "ru": "Богуслав", + "uk": "Богуслав", + "prefix": "місто", + "pl": "Bohusław" + }, + "lat": "49.54765", + "lon": "30.87330", + "id": 337555159, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Пестрецы", + "enName": "Pestretsy", + "names": { + "tt": "Питрәч", + "ru": "Пестрецы", + "ja": "ペストレツィ", + "lt": "Pestrecai" + }, + "lat": "55.75021", + "lon": "49.65709", + "id": 697241159, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Алексеевское", + "enName": "Alexeyevskoye", + "names": { + "tt": "Алексеевск", + "de": "Alexejewskoje", + "ru": "Алексеевское", + "ja": "アレクセーエフスコエ", + "lt": "Aleksėjevskojė" + }, + "lat": "55.30496", + "lon": "50.11289", + "id": 336524229, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "enName": "Lenina Street", + "names": { + "tt": "Ленин урамы", + "de": "uliza Lenina", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.30730", + "lon": "50.12300", + "id": 6508 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Мензелинск", + "enName": "Menzelinsk", + "names": { + "tt": "Минзәлә", + "de": "Menselinsk", + "ru": "Мензелинск", + "ja": "メンゼリンスク" + }, + "lat": "55.72556", + "lon": "53.10718", + "id": 337839017, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "enName": "Lenina Street", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.72411", + "lon": "53.10192", + "id": 3916 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Бугульма", + "enName": "Bugulma", + "names": { + "tt": "Бөгелмә", + "de": "Bugulma", + "ru": "Бугульма", + "ja": "ブグリマ" + }, + "lat": "54.53841", + "lon": "52.79559", + "id": 1707207947, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.54633", + "lon": "52.78989", + "id": 595 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Мамадыш", + "enName": "Mamadysh", + "names": { + "tt": "Мамадыш", + "de": "Mamadysch", + "ru": "Мамадыш", + "ja": "ママドィシ", + "az": "Mamadış" + }, + "lat": "55.70908", + "lon": "51.40891", + "id": 337839024, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица В. И. Ленина", + "lat": "55.71813", + "lon": "51.42443", + "id": 1740, + "buildings": [ + { + "name": "1/22", + "lat": "55.71805", + "lon": "51.42372", + "postcode": "422190" + }, + { + "name": "2", + "lat": "55.71847", + "lon": "51.42376" + }, + { + "name": "16", + "lat": "55.71888", + "lon": "51.42202" + }, + { + "name": "18/26", + "lat": "55.71893", + "lon": "51.42172" + }, + { + "name": "19", + "lat": "55.71890", + "lon": "51.41992" + }, + { + "name": "23", + "lat": "55.71911", + "lon": "51.41926" + }, + { + "name": "70", + "lat": "55.72104", + "lon": "51.41293" + }, + { + "name": "73", + "lat": "55.72167", + "lon": "51.40851" + }, + { + "name": "74", + "lat": "55.72127", + "lon": "51.41194" + }, + { + "name": "75А", + "lat": "55.72172", + "lon": "51.40578" + }, + { + "name": "75В", + "lat": "55.72161", + "lon": "51.40640" + }, + { + "name": "80", + "lat": "55.72155", + "lon": "51.41087" + }, + { + "name": "85А", + "lat": "55.72338", + "lon": "51.40188" + }, + { + "name": "94Б", + "lat": "55.72255", + "lon": "51.40617" + }, + { + "name": "96", + "lat": "55.72326", + "lon": "51.40492" + }, + { + "name": "104", + "lat": "55.72411", + "lon": "51.40293" + }, + { + "name": "105", + "lat": "55.72850", + "lon": "51.39870" + }, + { + "name": "107", + "lat": "55.72903", + "lon": "51.39891" + }, + { + "name": "107А", + "lat": "55.72898", + "lon": "51.39791" + }, + { + "name": "108", + "lat": "55.72640", + "lon": "51.40123", + "postcode": "422190" + }, + { + "name": "110", + "lat": "55.72655", + "lon": "51.40100", + "postcode": "422190" + }, + { + "name": "118", + "lat": "55.72943", + "lon": "51.39990", + "postcode": "422190" + } + ], + "intersectedStreets": [ + { + "name": "улица Толстого", + "lat": "55.71882", + "lon": "51.42155" + }, + { + "name": "улица Галактионова", + "lat": "55.71814", + "lon": "51.42443" + }, + { + "name": "улица Максимова", + "lat": "55.73760", + "lon": "51.39655" + }, + { + "name": "улица Абсалямова", + "names": { + "tt": "Әпсәләмов урамы" + }, + "lat": "55.73633", + "lon": "51.39705" + }, + { + "name": "Ипподромная улица", + "lat": "55.73581", + "lon": "51.39722" + }, + { + "name": "Товарищеская улица", + "lat": "55.73557", + "lon": "51.39728" + }, + { + "name": "улица Р. Нуриева", + "lat": "55.73485", + "lon": "51.39763" + }, + { + "name": "Вишнёвая улица", + "lat": "55.73407", + "lon": "51.39801" + }, + { + "name": "улица Энергетиков", + "lat": "55.73401", + "lon": "51.39803" + }, + { + "name": "улица Х. Такташ", + "lat": "55.73389", + "lon": "51.39810" + }, + { + "name": "Радужная улица", + "lat": "55.73326", + "lon": "51.39842" + }, + { + "name": "улица Дружбы", + "lat": "55.73326", + "lon": "51.39842" + }, + { + "name": "улица 50 лет Победы", + "lat": "55.73045", + "lon": "51.39947" + }, + { + "name": "улица Ш. Маннура", + "lat": "55.73006", + "lon": "51.39956" + }, + { + "name": "Майская улица", + "lat": "55.72997", + "lon": "51.39956" + }, + { + "name": "улица Нигматуллина", + "lat": "55.72785", + "lon": "51.40001" + }, + { + "name": "переулок Нигматуллина", + "lat": "55.72776", + "lon": "51.40003" + }, + { + "name": "улица Мичурина", + "lat": "55.72707", + "lon": "51.40037" + }, + { + "name": "Школьная улица", + "lat": "55.72677", + "lon": "51.40059" + }, + { + "name": "улица Ф. Энгельса", + "lat": "55.72602", + "lon": "51.40110" + }, + { + "name": "Садовая улица", + "lat": "55.72500", + "lon": "51.40188" + }, + { + "name": "Комсомольская улица", + "lat": "55.72367", + "lon": "51.40290" + }, + { + "name": "Союзная улица", + "lat": "55.72246", + "lon": "51.40591" + }, + { + "name": "улица Тукая", + "lat": "55.72201", + "lon": "51.40795" + }, + { + "name": "улица Азина", + "names": { + "tt": "Азин урамы" + }, + "lat": "55.72148", + "lon": "51.41020" + }, + { + "name": "Красноармейская улица", + "lat": "55.72093", + "lon": "51.41252" + }, + { + "name": "улица Гагарина", + "names": { + "tt": "Гагарин урамы" + }, + "lat": "55.72043", + "lon": "51.41469" + }, + { + "name": "улица М. Джалиля", + "lat": "55.71993", + "lon": "51.41677" + }, + { + "name": "улица К. Маркса", + "lat": "55.71946", + "lon": "51.41889" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Болгар", + "names": { + "tt": "Болгар", + "ja": "ボルガル" + }, + "lat": "55.41516", + "lon": "51.60141", + "id": 705268166, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.41721", + "lon": "51.59851", + "id": 9311, + "intersectedStreets": [ + { + "name": "улица Гагарина", + "lat": "55.41721", + "lon": "51.59851" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Бавлы", + "enName": "Bavly", + "names": { + "tt": "Баулы", + "de": "Bawly", + "ru": "Бавлы", + "ja": "バヴルィ", + "et": "Bavlõ" + }, + "lat": "54.40309", + "lon": "53.23571", + "id": 337839279, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "enName": "Lenina Street", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.40419", + "lon": "53.23696", + "id": 6957 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Згурівка", + "enName": "Zghurivka", + "names": { + "de": "Shuriwka", + "ru": "Згуровка", + "prefix": "селище міського типу", + "uk": "Згурівка", + "pl": "Zhuriwka" + }, + "lat": "50.49506", + "lon": "31.76918", + "id": 337520849, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Буинск", + "enName": "Buinsk", + "names": { + "tt": "Буа", + "de": "Buinsk", + "cv": "Пӑва", + "ru": "Буинск", + "ja": "ブインスク" + }, + "lat": "54.97229", + "lon": "48.29289", + "id": 295862317, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.96690", + "lon": "48.29476", + "id": 5936 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Гостомель", + "enName": "Hostomel", + "names": { + "ru": "Гостомель", + "uk": "Гостомель", + "prefix": "селище міського типу", + "pl": "Hostomel" + }, + "lat": "50.58826", + "lon": "30.25909", + "id": 337519314, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Бровари", + "enName": "Brovary", + "names": { + "de": "Browary", + "ru": "Бровары", + "prefix": "місто", + "uk": "Бровари", + "eo": "Brovari", + "fr": "Brovary", + "pl": "Browary", + "hu": "Brovari", + "et": "Brovarõ", + "sr": "Бровари" + }, + "lat": "50.51111", + "lon": "30.79004", + "id": 3673183717, + "type": "CITY", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Победа", + "names": { + "tt": "Победа" + }, + "lat": "54.67300", + "lon": "52.66657", + "id": 783002941, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "54.67386", + "lon": "52.66232", + "id": 4552 + } + ], + "matchStreet": 1 + }, + { + "name": "Димер", + "enName": "Dymer", + "names": { + "ru": "Дымер", + "uk": "Димер", + "prefix": "селище міського типу", + "pl": "Dymer" + }, + "lat": "50.78355", + "lon": "30.30780", + "id": 1585808480, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Набережные Челны", + "enName": "Naberezhnye Chelny", + "names": { + "tt": "Яр Чаллы", + "de": "Nabereschnyje Tschelny", + "no": "Naberezjnyje Tsjelny", + "be": "Наберажныя Чалны", + "fi": "Naberežnyje Tšelny", + "ru": "Набережные Челны", + "pt": "Naberejnye Chelny", + "bg": "Набережние Челни", + "lt": "Naberežnyje Čelnai", + "hr": "Naberežnye Čelny", + "lv": "Naberežnije Čelni", + "fr": "Naberejnye Tchelny", + "ka": "ნაბერეჟნიე ჩელნი", + "uk": "Набережні Човни", + "ca": "Nàberejnie_Txelní", + "sr": "Набережније Челни", + "sv": "Naberezjnyje Tjelny", + "ko": "나베레즈니예첼니", + "it": "Naberežnye Čelny", + "es": "Náberezhnye Chelny", + "zh": "卡马河畔切尔尼", + "et": "Naberežnõje Tšelnõ", + "cs": "Naberežnyje Čelny", + "ar": "نابريجناي تشلني", + "cv": "Чаллă", + "ja": "ナベレジヌイェ・チェルヌイ", + "az": "Naberejnıye Çelnı", + "fa": "نابرژنیه چلنی", + "pl": "Nabierieżnyje Czełny", + "ro": "Naberejnîe Celnî", + "nl": "Naberezjnye Tsjelny", + "tr": "Yarçallı", + "ba": "Яр Саллы" + }, + "lat": "55.74201", + "lon": "52.39923", + "id": 191749240, + "type": "CITY", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.69950", + "lon": "52.31059", + "id": 1334, + "buildings": [ + { + "name": "9", + "lat": "55.71643", + "lon": "52.38245" + } + ], + "intersectedStreets": [ + { + "name": "Полевая улица", + "lat": "55.69824", + "lon": "52.30953" + }, + { + "name": "улица Фрунзе", + "names": { + "tt": "Фрунзе урамы" + }, + "lat": "55.69824", + "lon": "52.30953" + }, + { + "name": "улица Карла Маркса", + "names": { + "tt": "Карл Маркс урамы" + }, + "lat": "55.69995", + "lon": "52.31029" + }, + { + "name": "Красногвардейская улица", + "lat": "55.70029", + "lon": "52.31009" + }, + { + "name": "Комсомольская набережная", + "lat": "55.70193", + "lon": "52.30900" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Богдашкино", + "enName": "Bogdashkino", + "names": { + "cv": "Пухтел", + "ru": "Богдашкино" + }, + "lat": "54.61321", + "lon": "47.67519", + "id": 336525269, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "переулок Ленина", + "lat": "54.61380", + "lon": "47.67824", + "id": 10951 + }, + { + "name": "улица Ленина", + "lat": "54.60684", + "lon": "47.67318", + "id": 9171 + } + ], + "matchStreet": 1 + }, + { + "name": "Сквира", + "enName": "Skvyra", + "names": { + "kk": "Skvyra", + "de": "Skwyra", + "ceb": "Skvyra", + "be": "Сквыра", + "ru": "Сквира", + "sv": "Skvyra", + "prefix": "місто", + "it": "Skvyra", + "fr": "Skvyra", + "cs": "Skvyra", + "hy": "Սկվյրա", + "vi": "Skvyra", + "uk": "Сквира", + "sk": "Skvyra", + "fa": "اسکویرا", + "pl": "Skwyra", + "ro": "Skvîra" + }, + "lat": "49.73370", + "lon": "29.66266", + "id": 337547206, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Рыбная Слобода", + "enName": "Rybnaya Sloboda", + "names": { + "tt": "Балык Бистәсе", + "ru": "Рыбная Слобода", + "ja": "ルィブナヤ・スロボーダ" + }, + "lat": "55.46442", + "lon": "50.14061", + "id": 703680750, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "tt-lat": "Lenin uramı", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.46726", + "lon": "50.14763", + "id": 2637 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Арск", + "enName": "Arsk", + "names": { + "tt": "Арча", + "de": "Arsk", + "ru": "Арск", + "ja": "アルスク" + }, + "lat": "56.09099", + "lon": "49.87718", + "id": 295868224, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Каркаусь", + "names": { + "tt": "Кәркәүч" + }, + "lat": "56.10411", + "lon": "51.14129", + "id": 797051488, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "lat": "56.10492", + "lon": "51.14228", + "id": 7812 + } + ], + "matchStreet": 1 + }, + { + "name": "Мусабай-Завод", + "names": { + "tt": "Мусабай-Завод", + "ru": "Мусабай-Завод" + }, + "lat": "55.51024", + "lon": "52.40260", + "id": 582657576, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.51051", + "lon": "52.40217", + "id": 7928, + "buildings": [ + { + "name": "29", + "lat": "55.51133", + "lon": "52.40182" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Тюлячи", + "enName": "Tyulyachi", + "names": { + "tt": "Теләче", + "ru": "Тюлячи", + "ja": "チュリャチ" + }, + "lat": "55.89188", + "lon": "50.23350", + "id": 697241170, + "type": "TOWN", + "listOfStreets": [ + { + "name": "улица Ленина", + "names": { + "tt": "Ленин урамы", + "de": "Leninstraße", + "be": "вулiца Ленiна", + "ru": "улица Ленина", + "mrj": "Ленин öлицä", + "fr": "Rue Lénine", + "ba": "Ленин урамы" + }, + "lat": "55.89223", + "lon": "50.24659", + "id": 3195 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Васильків", + "enName": "Vasylkiv", + "names": { + "de": "Wassylkiw", + "ru": "Васильков", + "uk": "Васильків", + "prefix": "місто", + "fr": "Vassylkiv", + "pl": "Wasilków", + "hu": "Vasylkiv" + }, + "lat": "50.17387", + "lon": "30.32158", + "id": 337527490, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + }, + { + "name": "Красный Ключ", + "enName": "Krasny Klyuch", + "names": { + "tt": "Красный Ключ", + "ja": "クラスヌイ・クリューチ" + }, + "lat": "55.68249", + "lon": "51.81914", + "id": 585272830, + "type": "TOWN", + "listOfStreets": [], + "init": 1 + } + ] +} \ No newline at end of file diff --git a/OsmAnd-java/src/test/resources/search/street_rue_emile.json b/OsmAnd-java/src/test/resources/search/street_rue_emile.json new file mode 100644 index 0000000000..74c6541b34 --- /dev/null +++ b/OsmAnd-java/src/test/resources/search/street_rue_emile.json @@ -0,0 +1,8178 @@ +{ + "settings": { + "lat": "50.64514", + "lon": "5.57342", + "radiusLevel": 1, + "totalLimit": -1, + "lang": "", + "transliterateIfMissing": false, + "emptyQueryAllowed": false, + "sortByName": false + }, + "phrase": "rue émile", + "results": [ + "Rue Émile Gérard, Liège", + "Rue Emile Vandervelde, Liège", + "Rue Emile Vandervelde - Rue Sainte-Marguerite, Liège", + "Rue Émile Zola (Laveu), Liège", + "Rue Émile Collard, Liège", + "Rue Emile Vandervelde, Vottem", + "Rue Emile Vandervelde (Vottem), Herstal", + "Rue Émile Verhaeren, Liège", + "Rue Émile Jeanne, Saint-Nicolas", + "Rue Emile Muraille, Herstal", + "Rue Emile Verhaeren, Grâce-Hollogne", + "Rue Emile Vinck, Herstal" + ], + "amenities": [ + { + "name": "Soye Rue Émile Lorent 252", + "lat": "50.44816", + "lon": "4.74122", + "id": 6096184636, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "bench_yes": "yes", + "covered_yes": "yes", + "network": "TECN", + "operator": "TEC" + } + }, + { + "name": "Rue Émile Pirson", + "lat": "50.50419", + "lon": "4.66955", + "id": 147817647, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "bridge_car": "yes" + } + }, + { + "name": "Soye Rue Émile Lorent 252", + "lat": "50.44883", + "lon": "4.74009", + "id": 6096184638, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "network": "TECN", + "operator": "TEC" + } + }, + { + "name": "4b", + "names": { + "fr": "Avenue Gabriel Emile Lebon", + "nl": "Gabriel Emile Lebonlaan" + }, + "lat": "50.82338", + "lon": "4.40886", + "id": 1222411455, + "subType": "lcn_ref", + "type": "transportation", + "additionalInfo": { + "network": "lcn", + "operator": "Brussels Mobility" + } + }, + { + "name": "Liège Avenue Emile Jenissen", + "lat": "50.64078", + "lon": "5.54797", + "id": 6057259982, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "network": "TECL", + "operator": "TEC" + } + }, + { + "name": "Orp-le-Grand Rue Emile Vandervelde", + "lat": "50.70772", + "lon": "4.99284", + "id": 4085539138, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "26", + "network": "TECB", + "operator": "TEC" + } + }, + { + "name": "Rue Emile Vandervelde", + "lat": "50.64840", + "lon": "5.54513", + "id": 57311075, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "bridge_car": "yes", + "ref": "N637" + } + }, + { + "name": "Namur Rue Émile Cuvelier", + "names": { + "etymology:wikidata": "Q2997972" + }, + "lat": "50.46461", + "lon": "4.86632", + "id": 3425215930, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "5, 8, 9", + "network": "TECN", + "operator": "TEC" + } + }, + { + "name": "Fleurus Rue Emile Vandervelde", + "lat": "50.47870", + "lon": "4.54611", + "id": 5994274194, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "network": "TECC", + "operator": "TEC" + } + }, + { + "name": "Fleurus Rue Emile Vandervelde", + "lat": "50.47895", + "lon": "4.54617", + "id": 5994274196, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "network": "TECC", + "operator": "TEC" + } + }, + { + "name": "Jezuïetencollege, heden stedelijke Emile Brauschool", + "lat": "51.05209", + "lon": "3.72417", + "id": 5252836298, + "subType": "building", + "type": "man_made", + "additionalInfo": { + "description": "Jezuïetencollege, heden stedelijke Emile Brauschool" + } + }, + { + "name": "Glain Rue Emile Vandervelde", + "lat": "50.64830", + "lon": "5.54829", + "id": 5838175460, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "network": "TECL", + "operator": "TEC" + } + } + ], + "cities": [ + { + "name": "Farciennes", + "names": { + "ru": "Фарсьен" + }, + "lat": "50.42732", + "lon": "4.55304", + "id": 357148969, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde (Pironchamps)", + "lat": "50.42581", + "lon": "4.52991", + "id": 148287 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Dour", + "names": { + "ru": "Дур" + }, + "lat": "50.39788", + "lon": "3.78069", + "id": 477168724, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Estièvenart", + "lat": "50.39535", + "lon": "3.77756", + "id": 120332 + }, + { + "name": "Rue Emile Cornez", + "lat": "50.40150", + "lon": "3.77275", + "id": 122499 + }, + { + "name": "Place Emile Vandervelde", + "lat": "50.39892", + "lon": "3.78561", + "id": 228077 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Wanze", + "names": { + "ru": "Ванз", + "wa": "Wônse", + "fr": "Wanze" + }, + "lat": "50.53326", + "lon": "5.21666", + "id": 353175792, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Rorive", + "lat": "50.54438", + "lon": "5.22288", + "id": 118883 + }, + { + "name": "Rue Emile Vandervelde (Vinalmont)", + "lat": "50.55988", + "lon": "5.23185", + "id": 196957 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Wavre", + "names": { + "ru": "Вавр", + "wa": "Wåve", + "fr": "Wavre", + "nl": "Waver" + }, + "lat": "50.71624", + "lon": "4.60844", + "id": 60284787, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Avenue Emile Verhaeren", + "lat": "50.70723", + "lon": "4.63664", + "id": 238410, + "intersectedStreets": [ + { + "name": "Avenue Maupassant", + "lat": "50.70711", + "lon": "4.63584" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Gingelom", + "lat": "50.75003", + "lon": "5.13321", + "id": 1736682348, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Beauduinstraat (Jeuk)", + "lat": "50.71964", + "lon": "5.18898", + "id": 201151, + "intersectedStreets": [ + { + "name": "Kasteelstraat (Jeuk)", + "lat": "50.72168", + "lon": "5.18810" + }, + { + "name": "Spoorwegstraat (Jeuk)", + "lat": "50.72168", + "lon": "5.18810" + }, + { + "name": "Kustrijkstraat (Jeuk)", + "lat": "50.72407", + "lon": "5.18711" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Visé", + "names": { + "de": "Weset", + "ru": "Визе", + "wa": "Vizé", + "fr": "Visé", + "nl": "Wezet" + }, + "lat": "50.73366", + "lon": "5.69553", + "id": 79453332, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Verhaeren", + "lat": "50.74042", + "lon": "5.68075", + "id": 236252 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Wauthier-Braine", + "names": { + "fr": "Wauthier-Braine", + "nl": "Woutersbrakel" + }, + "lat": "50.67974", + "lon": "4.30123", + "id": 294066552, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Schampaert (La Cantine)", + "lat": "50.68466", + "lon": "4.29962", + "id": 43680 + }, + { + "name": "Rue Emile Vandervelde (La Cantine)", + "lat": "50.68732", + "lon": "4.30550", + "id": 43683 + } + ], + "matchStreet": 1 + }, + { + "name": "Koksijde", + "names": { + "ru": "Коксейде", + "fr": "Coxyde", + "vls": "Koksyde", + "nl": "Koksijde" + }, + "lat": "51.10642", + "lon": "2.65002", + "id": 720858823, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Verhaerenlaan (Oostduinkerke)", + "names": { + "alt_name": "Verhaerenlaan (Oostduinkerke)" + }, + "lat": "51.12650", + "lon": "2.69611", + "id": 132769 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Sint-Jozef", + "names": { + "fr": "Saint-Joseph", + "vls": "Sint-Jozef", + "nl": "Sint-Jozef" + }, + "lat": "51.22561", + "lon": "3.23163", + "id": 1677380553, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Emile en Dora Rommelaerestraat", + "lat": "51.22929", + "lon": "3.24316", + "id": 132717 + } + ], + "matchStreet": 1 + }, + { + "name": "Guillemins", + "lat": "50.62513", + "lon": "5.57138", + "id": 23620263899, + "type": "SUBURB", + "listOfStreets": [ + { + "name": "Avenue Emile Digneffe", + "lat": "50.61993", + "lon": "5.57415", + "id": 188340, + "buildings": [], + "intersectedStreets": [ + { + "name": "Place du Général Leman", + "lat": "50.61993", + "lon": "5.57415" + }, + { + "name": "Rue Ernest Solvay", + "lat": "50.61982", + "lon": "5.57425" + }, + { + "name": "Quai de Rome", + "lat": "50.62054", + "lon": "5.57756" + }, + { + "name": "Rue de Fétinne", + "lat": "50.62054", + "lon": "5.57756" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Saint-Gilles - Sint-Gillis", + "names": { + "ru": "Сен-Жиль", + "fr": "Saint-Gilles", + "nl": "Sint-Gillis" + }, + "lat": "50.82674", + "lon": "4.34567", + "id": 26032499, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Feron - Émile Feronstraat", + "names": { + "old_name:fr": "Rue de Constantinople", + "old_name": "Rue de Constantinople - Konstantinopelstraat", + "old_name:nl": "Konstantinopelstraat", + "fr": "Rue Émile Feron", + "nl": "Émile Feronstraat" + }, + "lat": "50.83070", + "lon": "4.33595", + "id": 13792 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Mouscron", + "names": { + "pcd": "Moucron", + "ru": "Мускрон", + "lt": "Mukronas", + "fr": "Mouscron", + "vls": "Meschroên", + "nl": "Moeskroen" + }, + "lat": "50.74333", + "lon": "3.21391", + "id": 539515887, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Cité Émile Vinck", + "lat": "50.72855", + "lon": "3.20155", + "id": 113891 + }, + { + "name": "Rue Émile Zola", + "lat": "50.74942", + "lon": "3.18342", + "id": 105815 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Dilbeek", + "names": { + "ru": "Дилбек" + }, + "lat": "50.84807", + "lon": "4.26626", + "id": 251470517, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Eylenboschstraat", + "lat": "50.83394", + "lon": "4.19261", + "id": 166356 + }, + { + "name": "Emile de Blutslaan", + "lat": "50.86510", + "lon": "4.26488", + "id": 58170 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Baelen", + "names": { + "ru": "Бален", + "wa": "Bailou", + "fr": "Baelen" + }, + "lat": "50.63119", + "lon": "5.97150", + "id": 240115271, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Schmuck", + "lat": "50.63143", + "lon": "5.97678", + "id": 103467 + } + ], + "matchStreet": 1 + }, + { + "name": "Montignies-sur-Sambre", + "lat": "50.40068", + "lon": "4.48036", + "id": 2603284194, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Dutrieux", + "lat": "50.40027", + "lon": "4.48126", + "id": 123422 + }, + { + "name": "Rue Émile Tumelaire", + "lat": "50.40861", + "lon": "4.45315", + "id": 245342 + }, + { + "name": "Place Emile Vandevelde", + "lat": "50.41527", + "lon": "4.46157", + "id": 122634 + } + ], + "matchStreet": 1 + }, + { + "name": "Oupeye", + "names": { + "ru": "Упей", + "wa": "Oûpêye", + "fr": "Oupeye" + }, + "lat": "50.70919", + "lon": "5.64506", + "id": 1690252401, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vinck", + "lat": "50.70865", + "lon": "5.64131", + "id": 117951 + }, + { + "name": "Rue Emile Vandervelde (Vivegnis)", + "lat": "50.70251", + "lon": "5.64957", + "id": 117273 + }, + { + "name": "Rue Emile de Laveleye", + "lat": "50.70558", + "lon": "5.67530", + "id": 190178 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Seraing", + "names": { + "ru": "Серен", + "lt": "Serenas", + "wa": "Serè", + "fr": "Seraing" + }, + "lat": "50.59664", + "lon": "5.50833", + "id": 79443055, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Royer (Jemeppe-Sur-Meuse)", + "lat": "50.61251", + "lon": "5.49261", + "id": 53325 + }, + { + "name": "Rue Émile Zola", + "lat": "50.58117", + "lon": "5.50091", + "id": 34019 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Onhaye", + "names": { + "ru": "Оне", + "wa": "Onhaye", + "fr": "Onhaye" + }, + "lat": "50.24357", + "lon": "4.84087", + "id": 247722706, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Collard", + "lat": "50.23974", + "lon": "4.75824", + "id": 151309 + }, + { + "name": "Rue Emile Toussaint", + "lat": "50.25029", + "lon": "4.78133", + "id": 167850 + } + ], + "matchStreet": 1 + }, + { + "name": "Weerde", + "lat": "50.97228", + "lon": "4.47987", + "id": 270762994, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Verbruggestraat", + "lat": "50.97490", + "lon": "4.47656", + "id": 49036 + } + ], + "matchStreet": 1 + }, + { + "name": "La Louvière", + "names": { + "ru": "Ла-Лувьер" + }, + "lat": "50.47949", + "lon": "4.18528", + "id": 64850883, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Nève", + "lat": "50.47104", + "lon": "4.19873", + "id": 158756 + }, + { + "name": "Rue Emile Cambier", + "lat": "50.45727", + "lon": "4.19678", + "id": 159836 + }, + { + "name": "Rue Emile Tilmant", + "lat": "50.47594", + "lon": "4.21787", + "id": 158867 + }, + { + "name": "Rue Émile Urbain", + "lat": "50.46547", + "lon": "4.16828", + "id": 238032 + }, + { + "name": "Rue Emile Malbecq", + "lat": "50.47704", + "lon": "4.18886", + "id": 82488 + }, + { + "name": "Rue Emile Cornez (Trivières)", + "lat": "50.44985", + "lon": "4.14556", + "id": 159486 + }, + { + "name": "Rue Emile Latteur (Saint-Vaast)", + "lat": "50.45186", + "lon": "4.15521", + "id": 219057 + }, + { + "name": "rue Emile Verhaeren", + "lat": "50.47018", + "lon": "4.17577", + "id": 158681 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Braine-le-Comte", + "names": { + "ru": "Брен-ле-Конт", + "fr": "Braine-Le-Comte", + "nl": "'s-Gravenbrakel" + }, + "lat": "50.61235", + "lon": "4.14251", + "id": 476260354, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Émile Herman", + "lat": "50.60729", + "lon": "4.13861", + "id": 23680 + }, + { + "name": "Rue Emile Heuchon", + "lat": "50.60741", + "lon": "4.14037", + "id": 36681 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Fontenoy", + "lat": "50.56789", + "lon": "3.47337", + "id": 257106680, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Vandervelde", + "lat": "50.56195", + "lon": "3.47696", + "id": 236155 + } + ], + "matchStreet": 1 + }, + { + "name": "Verviers", + "names": { + "ru": "Вервье", + "lt": "Vervjė", + "wa": "Vervî", + "fr": "Verviers" + }, + "lat": "50.59323", + "lon": "5.86385", + "id": 79451344, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Lelarge", + "lat": "50.58766", + "lon": "5.88348", + "id": 123640 + }, + { + "name": "Avenue Emile Jouret", + "lat": "50.59029", + "lon": "5.88942", + "id": 123665, + "intersectedStreets": [ + { + "name": "Rue de Julien Jardon", + "lat": "50.59029", + "lon": "5.88942" + }, + { + "name": "Avenue Fernand Desonay", + "lat": "50.59013", + "lon": "5.89043" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Faimes", + "names": { + "wa": "Faime", + "fr": "Faimes" + }, + "lat": "50.66525", + "lon": "5.26232", + "id": 734852986, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde (Borlez)", + "lat": "50.63138", + "lon": "5.24486", + "id": 149458 + } + ], + "matchStreet": 1 + }, + { + "name": "Antwerpen", + "enName": "Antwerp", + "names": { + "de": "Antwerpen", + "fi": "Antwerpen", + "ru": "Антверпен", + "pt": "Antuérpia", + "lt": "Antverpenas", + "hr": "Antwerpen", + "lv": "Antverpene", + "wa": "Anverse", + "fr": "Anvers", + "hy": "Անտվերպեն", + "ka": "ანტვერპენი", + "uk": "Антверпен", + "sk": "Antverpy", + "ca": "Anvers", + "sr": "Антверпен", + "kn": "ಆಂಟ್ವೆರ್ಪ್", + "el": "Αμβέρσα", + "it": "Anversa", + "es": "Amberes", + "zh": "安特卫普", + "cs": "Antverpy", + "ar": "أنتويرب", + "la": "Antverpia", + "ja": "アントワープ", + "nds": "Antwarp", + "pl": "Antwerpia", + "he": "אנטוורפן", + "nl": "Antwerpen" + }, + "lat": "51.22111", + "lon": "4.39970", + "id": 1765433658, + "type": "CITY", + "listOfStreets": [ + { + "name": "Emile Verhaerenlaan (Linkeroever)", + "lat": "51.22387", + "lon": "4.37848", + "id": 61594 + }, + { + "name": "Emile Dancolaan", + "lat": "51.21936", + "lon": "4.36685", + "id": 206033 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Heysel - Heizel", + "names": { + "fr": "Heysel", + "nl": "Heizel" + }, + "lat": "50.88888", + "lon": "4.34072", + "id": 1866455868, + "type": "SUBURB", + "listOfStreets": [ + { + "name": "Rue Emile Wauters - Emile Wautersstraat", + "lat": "50.88866", + "lon": "4.34254", + "id": 247336 + } + ], + "matchStreet": 1 + }, + { + "name": "Nethen", + "lat": "50.78382", + "lon": "4.67550", + "id": 293576951, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.78205", + "lon": "4.69024", + "id": 137436 + } + ], + "matchStreet": 1 + }, + { + "name": "Halluin", + "names": { + "nl": "Halewijn" + }, + "lat": "50.78319", + "lon": "3.12722", + "id": 26692038, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Zola", + "lat": "50.78361", + "lon": "3.12263", + "id": 121895 + }, + { + "name": "Cité Émile Verroye", + "lat": "50.78419", + "lon": "3.13293", + "id": 121914 + }, + { + "name": "rue Emile Bostoen", + "lat": "50.78495", + "lon": "3.13166", + "id": 247260 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Vieille-Maison", + "lat": "50.53932", + "lon": "4.61679", + "id": 642194961, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Rue Emile Pirson", + "lat": "50.53963", + "lon": "4.61565", + "id": 39356 + } + ], + "matchStreet": 1 + }, + { + "name": "Genappe", + "names": { + "ru": "Женап", + "fr": "Genappe", + "nl": "Genepiën" + }, + "lat": "50.61087", + "lon": "4.45107", + "id": 60115919, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Hecq", + "lat": "50.61219", + "lon": "4.46828", + "id": 30825 + }, + { + "name": "Rue Emile Boucqueau", + "lat": "50.61158", + "lon": "4.46599", + "id": 179591 + }, + { + "name": "Rue Emile François", + "lat": "50.64939", + "lon": "4.48094", + "id": 64147 + }, + { + "name": "Rue Émile Marcq", + "lat": "50.61772", + "lon": "4.44925", + "id": 87922 + }, + { + "name": "Rue Emile Vandevandel", + "lat": "50.61272", + "lon": "4.44888", + "id": 43911 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Ath", + "names": { + "ru": "Ат", + "fr": "Ath", + "nl": "Aat" + }, + "lat": "50.63114", + "lon": "3.77696", + "id": 33372516, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Carlier (Irchonwelz)", + "lat": "50.61832", + "lon": "3.77217", + "id": 135237 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Corbais", + "names": { + "ru": "Корбе", + "wa": "Côrbåy" + }, + "lat": "50.64565", + "lon": "4.65552", + "id": 293578193, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Francqui", + "lat": "50.65690", + "lon": "4.62379", + "id": 12455, + "buildings": [] + }, + { + "name": "Rue Emile Francqui", + "lat": "50.65896", + "lon": "4.62312", + "id": 14520 + }, + { + "name": "Clos Émile Fabry", + "lat": "50.64845", + "lon": "4.66016", + "id": 81198, + "intersectedStreets": [ + { + "name": "Rue de la Dîme", + "lat": "50.64780", + "lon": "4.65837" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Grand-Hallet", + "lat": "50.69412", + "lon": "5.03406", + "id": 719039029, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Duchesne", + "lat": "50.69487", + "lon": "5.03067", + "id": 22848 + } + ], + "matchStreet": 1 + }, + { + "name": "Quaregnon", + "names": { + "pcd": "Cwargnon", + "ru": "Кареньон", + "fr": "Quaregnon" + }, + "lat": "50.44218", + "lon": "3.86369", + "id": 1144636557, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde (Les Six Chemins)", + "lat": "50.43075", + "lon": "3.86841", + "id": 167416 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Nivelles", + "names": { + "ru": "Нивель", + "fr": "Nivelles", + "nl": "Nijvel" + }, + "lat": "50.59769", + "lon": "4.32359", + "id": 60285677, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.59572", + "lon": "4.33134", + "id": 35277 + }, + { + "name": "Place Émile de Lalieux", + "lat": "50.59849", + "lon": "4.32797", + "id": 76307 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Honnelles", + "names": { + "pcd": "Onele", + "fr": "Honnelles" + }, + "lat": "50.35209", + "lon": "3.73102", + "id": 670209871, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Cornez", + "lat": "50.36936", + "lon": "3.69823", + "id": 172060 + }, + { + "name": "Rue Emile Delgrange", + "lat": "50.35636", + "lon": "3.77730", + "id": 164479 + } + ], + "matchStreet": 1 + }, + { + "name": "Colpach-Haut", + "names": { + "de": "Obercolpach", + "lb": "Uewerkolpech", + "fr": "Colpach-Haut" + }, + "lat": "49.76739", + "lon": "5.81979", + "id": 267038552, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Aline an Émile Mayrischstrooss", + "lat": "49.76605", + "lon": "5.82065", + "id": 23032 + } + ], + "matchStreet": 1 + }, + { + "name": "Huy", + "names": { + "ar": "هوي", + "ru": "Юи", + "bg": "Юи", + "uk": "Юї", + "ja": "ユイ", + "wa": "Hu", + "fa": "اویی", + "fr": "Huy", + "li": "Hoei", + "nl": "Hoei", + "zh": "于伊" + }, + "lat": "50.52154", + "lon": "5.23574", + "id": 60287767, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Cité Émile Vierset", + "lat": "50.52932", + "lon": "5.24544", + "id": 175075, + "intersectedStreets": [ + { + "name": "Rue des Vergiers", + "lat": "50.52850", + "lon": "5.24689" + } + ] + }, + { + "name": "Rue Emile Vandervelde (Gives)", + "lat": "50.50630", + "lon": "5.15381", + "id": 75650 + }, + { + "name": "Rue Emile Delperée", + "lat": "50.52569", + "lon": "5.24541", + "id": 47016 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Gussignies", + "lat": "50.33709", + "lon": "3.74125", + "id": 315908694, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Sohier", + "lat": "50.33745", + "lon": "3.74218", + "id": 119435 + } + ], + "matchStreet": 1 + }, + { + "name": "Lodelinsart", + "lat": "50.43186", + "lon": "4.44895", + "id": 674001006, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde", + "lat": "50.42566", + "lon": "4.42751", + "id": 123273 + } + ], + "matchStreet": 1 + }, + { + "name": "Carlsbourg", + "names": { + "ru": "Карльсбур" + }, + "lat": "49.89476", + "lon": "5.08304", + "id": 416323675, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Gardez", + "lat": "49.89426", + "lon": "5.08167", + "id": 182691 + } + ], + "matchStreet": 1 + }, + { + "name": "Schaerbeek - Schaarbeek", + "names": { + "ru": "Схарбек", + "fr": "Schaerbeek", + "nl": "Schaarbeek" + }, + "lat": "50.86761", + "lon": "4.37372", + "id": 66169638, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Square Emile Duployé - Emile Duployésquare", + "names": { + "fr": "Square Emile Duployé", + "nl": "Emile Duployésquare" + }, + "lat": "50.85861", + "lon": "4.37925", + "id": 3201 + }, + { + "name": "Avenue Emile Zola - Emile Zolalaan", + "names": { + "fr": "Avenue Emile Zola", + "nl": "Emile Zolalaan" + }, + "lat": "50.87717", + "lon": "4.38000", + "id": 3385 + }, + { + "name": "Avenue Emile Verhaeren - Emile Verhaerenlaan", + "names": { + "fr": "Avenue Emile Verhaeren", + "nl": "Emile Verhaerenlaan" + }, + "lat": "50.87493", + "lon": "4.37827", + "id": 3443 + }, + { + "name": "Avenue Emile Max - Emile Maxlaan", + "names": { + "fr": "Avenue Emile Max", + "nl": "Emile Maxlaan" + }, + "lat": "50.85170", + "lon": "4.39951", + "id": 2675 + }, + { + "name": "Rue Emile Wittmann - Emile Wittmannstraat", + "names": { + "fr": "Rue Emile Wittmann", + "nl": "Emile Wittmannstraat" + }, + "lat": "50.85478", + "lon": "4.39033", + "id": 3039 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Lambusart", + "names": { + "ru": "Ламбюзар" + }, + "lat": "50.45729", + "lon": "4.55214", + "id": 359135665, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Hautem", + "lat": "50.46283", + "lon": "4.55437", + "id": 128219 + } + ], + "matchStreet": 1 + }, + { + "name": "Nieuwpoort", + "names": { + "ru": "Ньивпорт", + "fr": "Nieuport" + }, + "lat": "51.13039", + "lon": "2.75167", + "id": 1651552938, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Verhaerenlaan (Nieuwpoort-Bad)", + "lat": "51.14557", + "lon": "2.72343", + "id": 102070 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Houplines", + "lat": "50.69046", + "lon": "2.91041", + "id": 26694926, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Zola", + "lat": "50.69754", + "lon": "2.91324", + "id": 132629 + }, + { + "name": "Rue Émile Verhaeren", + "lat": "50.69593", + "lon": "2.91936", + "id": 132643 + } + ], + "matchStreet": 1 + }, + { + "name": "Trivières", + "names": { + "ru": "Тривьер" + }, + "lat": "50.45174", + "lon": "4.14760", + "id": 1678307480, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Cornez", + "lat": "50.44985", + "lon": "4.14556", + "id": 159487 + } + ], + "matchStreet": 1 + }, + { + "name": "Juprelle", + "names": { + "ru": "Жюпрель", + "wa": "Djouprele", + "fr": "Juprelle" + }, + "lat": "50.71069", + "lon": "5.52906", + "id": 305244882, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Grisard", + "lat": "50.72704", + "lon": "5.50870", + "id": 242688 + } + ], + "matchStreet": 1 + }, + { + "name": "Boussu", + "names": { + "pcd": "Boussu-dlé-Mont", + "ru": "Буссю", + "wa": "Boussu-dlé-Mont", + "fr": "Boussu" + }, + "lat": "50.43313", + "lon": "3.79610", + "id": 476259459, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Hornu)", + "lat": "50.42739", + "lon": "3.83114", + "id": 203654 + }, + { + "name": "Rue Emile Verhaeren (Boussu-Bois)", + "lat": "50.41341", + "lon": "3.79129", + "id": 121818 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Chaudfontaine", + "names": { + "ru": "Шофонтен", + "wa": "Tchôfontinne", + "fr": "Chaudfontaine" + }, + "lat": "50.58479", + "lon": "5.64697", + "id": 360038808, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde (Vaux-sous-Chèvremont)", + "lat": "50.59878", + "lon": "5.63187", + "id": 65468 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Beuzet", + "names": { + "ru": "Бовес", + "wa": "Beuzet", + "fr": "Beuzet", + "nl": "Beuzet" + }, + "lat": "50.53298", + "lon": "4.74815", + "id": 310104730, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Dewez (Ferooz)", + "lat": "50.54048", + "lon": "4.73045", + "id": 62989 + } + ], + "matchStreet": 1 + }, + { + "name": "Halle", + "names": { + "ru": "Халле", + "fr": "Hal", + "nl": "Halle" + }, + "lat": "50.73605", + "lon": "4.23744", + "id": 1677365023, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Dr. Emile Gallemaertstraat", + "lat": "50.74064", + "lon": "4.22903", + "id": 42042 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Serville", + "names": { + "wa": "Serveye" + }, + "lat": "50.25007", + "lon": "4.78066", + "id": 518834700, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Rue Emile Toussaint", + "lat": "50.25028", + "lon": "4.78133", + "id": 167851 + } + ], + "matchStreet": 1 + }, + { + "name": "Châtelet", + "names": { + "ru": "Шатле" + }, + "lat": "50.40461", + "lon": "4.52435", + "id": 64619244, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Hermant", + "lat": "50.38974", + "lon": "4.51598", + "id": 124065 + }, + { + "name": "Rue Saint-François - Rue Émile Vandervelde", + "names": { + "left": "Rue Émile Vandervelde", + "right": "Rue Saint-François" + }, + "lat": "50.43105", + "lon": "4.52036", + "id": 148295 + }, + { + "name": "Rue Émile Vandervelde (Bouffioulx)", + "lat": "50.39502", + "lon": "4.51879", + "id": 123784 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Queue-du-Bois", + "lat": "50.63715", + "lon": "5.67858", + "id": 746916917, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.63814", + "lon": "5.67654", + "id": 104874 + } + ], + "matchStreet": 1 + }, + { + "name": "Woluwe-Saint-Pierre - Sint-Pieters-Woluwe", + "names": { + "ru": "Волюве-Сен-Пьер", + "fr": "Woluwe-Saint-Pierre", + "nl": "Sint-Pieters-Woluwe" + }, + "lat": "50.82925", + "lon": "4.44330", + "id": 66508549, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Avenue Gabriel Emile Lebon - Gabriel Emile Lebonlaan", + "lat": "50.82654", + "lon": "4.40603", + "id": 14772 + }, + { + "name": "Avenue Emile Laine - Emile Lainelaan", + "names": { + "fr": "Avenue Emile Laine", + "nl": "Emile Lainelaan" + }, + "lat": "50.82515", + "lon": "4.42507", + "id": 36359 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Soumagne", + "names": { + "ru": "Сумань", + "wa": "Soûmagne", + "fr": "Soumagne" + }, + "lat": "50.61477", + "lon": "5.73995", + "id": 457067470, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Clos Emile Herman", + "lat": "50.65572", + "lon": "5.71647", + "id": 243685, + "intersectedStreets": [ + { + "name": "Rue Joly (Heuseux)", + "lat": "50.65545", + "lon": "5.71727" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Fontaine-l'Évêque", + "names": { + "ru": "Фонтен-л’Эвек" + }, + "lat": "50.41178", + "lon": "4.32462", + "id": 64804585, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Marcq", + "lat": "50.39729", + "lon": "4.33018", + "id": 173806 + }, + { + "name": "Rue Émile Vandervelde (Forchies-la-Marche)", + "lat": "50.43091", + "lon": "4.32445", + "id": 91952 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Beyne-Heusay", + "names": { + "ru": "Бен-Эзе", + "wa": "Binne-Heuzea", + "fr": "Beyne-Heusay" + }, + "lat": "50.62206", + "lon": "5.65341", + "id": 746916916, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Queue-du-Bois)", + "lat": "50.63814", + "lon": "5.67654", + "id": 104873 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Montigny-le-Tilleul", + "names": { + "ru": "Монтиньи-ле-Тийёль" + }, + "lat": "50.37916", + "lon": "4.37968", + "id": 1024116145, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde", + "lat": "50.38083", + "lon": "4.38329", + "id": 122642 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Hensies", + "names": { + "pcd": "Inzî", + "ru": "Анси", + "fr": "Hensies" + }, + "lat": "50.43284", + "lon": "3.68471", + "id": 481030226, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Thulin)", + "lat": "50.42997", + "lon": "3.74215", + "id": 176291 + } + ], + "matchStreet": 1 + }, + { + "name": "De Panne", + "names": { + "ru": "Де-Панне", + "fr": "La Panne", + "nl": "De Panne" + }, + "lat": "51.09883", + "lon": "2.59241", + "id": 79395891, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Verhaerenlaan", + "lat": "51.09528", + "lon": "2.56979", + "id": 162787 + } + ], + "matchStreet": 1 + }, + { + "name": "Bouffioulx", + "lat": "50.39021", + "lon": "4.51519", + "id": 673961151, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Hermant", + "lat": "50.38972", + "lon": "4.51598", + "id": 124066 + } + ], + "matchStreet": 1 + }, + { + "name": "Saint-Nicolas", + "names": { + "ru": "Сен-Никола", + "wa": "Sint-Nicolêye", + "fr": "Saint-Nicolas" + }, + "lat": "50.63053", + "lon": "5.53943", + "id": 469371619, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Place Emile Vandervelde (Montegnée)", + "lat": "50.63682", + "lon": "5.51670", + "id": 53314, + "intersectedStreets": [ + { + "name": "Rue François Cloes", + "lat": "50.63694", + "lon": "5.51702" + }, + { + "name": "Rue Xhavée (Montegnée)", + "lat": "50.63706", + "lon": "5.51681" + }, + { + "name": "Rue Chantraine (Montegnée)", + "lat": "50.63655", + "lon": "5.51642" + }, + { + "name": "Rue Fays (Montegnée)", + "lat": "50.63686", + "lon": "5.51705" + } + ] + }, + { + "name": "Rue Émile Jeanne", + "lat": "50.63388", + "lon": "5.51915", + "id": 33528 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Ham-sur-Sambre", + "names": { + "wa": "Han-so-Sambe", + "nl": "Ham-sur-Sambre" + }, + "lat": "50.44449", + "lon": "4.67348", + "id": 688355304, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.44396", + "lon": "4.67666", + "id": 136393 + } + ], + "matchStreet": 1 + }, + { + "name": "Tamines", + "names": { + "ru": "Тамин", + "wa": "Tamène", + "nl": "Tamines" + }, + "lat": "50.43410", + "lon": "4.60758", + "id": 482112928, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Duculot", + "lat": "50.43542", + "lon": "4.60885", + "id": 245797 + } + ], + "matchStreet": 1 + }, + { + "name": "Mornimont", + "names": { + "wa": "Mornîmont", + "nl": "Mornimont" + }, + "lat": "50.45552", + "lon": "4.70434", + "id": 688355309, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrefour Emile Stasse", + "lat": "50.44918", + "lon": "4.70290", + "id": 173136, + "intersectedStreets": [ + { + "name": "Rue du Longwez", + "lat": "50.44920", + "lon": "4.70290" + } + ] + }, + { + "name": "Rue Emile Tréfois", + "lat": "50.45629", + "lon": "4.70507", + "id": 200222 + } + ], + "matchStreet": 1 + }, + { + "name": "Jemeppe-sur-Sambre", + "names": { + "ru": "Жемеп-сюр-Самбр", + "wa": "Djimepe-so-Sambe", + "fr": "Jemeppe-sur-Sambre", + "nl": "Jemeppe-sur-Sambre" + }, + "lat": "50.46564", + "lon": "4.66867", + "id": 688355308, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrefour Emile Stasse", + "lat": "50.44917", + "lon": "4.70288", + "id": 173135, + "intersectedStreets": [ + { + "name": "Rue du Longwez", + "lat": "50.44917", + "lon": "4.70288" + } + ] + }, + { + "name": "Rue Emile Vandervelde (Ham-sur-Sambre)", + "lat": "50.44395", + "lon": "4.67666", + "id": 136392 + }, + { + "name": "Rue Emile Matelart (Saint-Martin)", + "lat": "50.50458", + "lon": "4.65080", + "id": 66955 + }, + { + "name": "Rue Emile Tréfois", + "lat": "50.45627", + "lon": "4.70507", + "id": 200221 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Flémalle", + "names": { + "ru": "Флемаль", + "wa": "Flémåle", + "fr": "Flémalle" + }, + "lat": "50.60228", + "lon": "5.45808", + "id": 746110293, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Place Emile Zola", + "lat": "50.56853", + "lon": "5.45342", + "id": 49266, + "intersectedStreets": [ + { + "name": "Avenue du Gros Chêne", + "lat": "50.56829", + "lon": "5.45325" + }, + { + "name": "Rue Bois des Galants", + "lat": "50.56819", + "lon": "5.45377" + }, + { + "name": "Rue des Penseurs", + "lat": "50.56819", + "lon": "5.45377" + } + ] + }, + { + "name": "Rue Paul-Emile Janson", + "lat": "50.60171", + "lon": "5.47859", + "id": 33226 + }, + { + "name": "Rue Emile Vandervelde", + "lat": "50.60092", + "lon": "5.47216", + "id": 49620 + }, + { + "name": "Place Emile Vinck", + "lat": "50.60218", + "lon": "5.44673", + "id": 49639, + "intersectedStreets": [ + { + "name": "Avenue de la Chapelle (Flémalle-Haute)", + "lat": "50.60160", + "lon": "5.44546" + }, + { + "name": "Chemin des Maisons", + "lat": "50.60160", + "lon": "5.44546" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Saint-Martin", + "names": { + "wa": "Sint-Mårtén", + "nl": "Saint-Martin" + }, + "lat": "50.50084", + "lon": "4.64726", + "id": 688355307, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Matelart", + "lat": "50.50192", + "lon": "4.64945", + "id": 232330 + } + ], + "matchStreet": 1 + }, + { + "name": "Paliseul", + "names": { + "wa": "Palijhoû", + "fr": "Paliseul" + }, + "lat": "49.90353", + "lon": "5.13491", + "id": 416323008, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Gardez", + "lat": "49.89144", + "lon": "5.08169", + "id": 182678 + } + ], + "matchStreet": 1 + }, + { + "name": "Flémalle-Haute", + "names": { + "ru": "Флемаль-От", + "wa": "Li Hôte Flémåle", + "fr": "Flémalle-Haute" + }, + "lat": "50.60184", + "lon": "5.44746", + "id": 309278555, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Vinck", + "lat": "50.60218", + "lon": "5.44673", + "id": 49640, + "intersectedStreets": [ + { + "name": "Avenue de la Chapelle", + "lat": "50.60160", + "lon": "5.44546" + }, + { + "name": "Chemin des Maisons", + "lat": "50.60160", + "lon": "5.44546" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Lincent", + "names": { + "ru": "Ленсан", + "wa": "Lîssin", + "fr": "Lincent", + "nl": "Lijsem" + }, + "lat": "50.71161", + "lon": "5.03182", + "id": 470229233, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Looze (Pellaines)", + "lat": "50.71582", + "lon": "4.99895", + "id": 225610 + } + ], + "matchStreet": 1 + }, + { + "name": "Waterloo", + "names": { + "ru": "Ватерлоо" + }, + "lat": "50.71736", + "lon": "4.39781", + "id": 130692807, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Avenue Émile Theys", + "lat": "50.71709", + "lon": "4.38814", + "id": 35338 + }, + { + "name": "Rue Émile Dury", + "lat": "50.70628", + "lon": "4.37805", + "id": 9738 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Bomal", + "lat": "50.66814", + "lon": "4.87353", + "id": 293436466, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Laurent", + "lat": "50.66652", + "lon": "4.87368", + "id": 232187, + "intersectedStreets": [ + { + "name": "Rue de Laloux", + "lat": "50.66685", + "lon": "4.87426" + }, + { + "name": "Rue de l'Abyme", + "lat": "50.66645", + "lon": "4.87342" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Liège", + "names": { + "de": "Lüttich", + "ru": "Льеж", + "ast": "Liex", + "bg": "Льеж", + "lt": "Lježas", + "lv": "Lježa", + "wa": "Lîdje", + "fr": "Liège", + "fy": "Luik", + "oc": "Lièja", + "uk": "Льєж", + "ca": "Lieja", + "sr": "Лијеж", + "kk": "Льеж", + "ko": "리에주", + "gl": "Liexa", + "mr": "लीज", + "pnb": "لیج", + "el": "Λιέγη", + "eo": "Lieĝo", + "it": "Liegi", + "es": "Lieja", + "zh": "列日", + "cs": "Lutych", + "eu": "Lieja", + "ar": "لييج", + "th": "ลีแยฌ", + "la": "Leodium", + "lb": "Léck", + "ja": "リエージュ", + "fa": "لیژ", + "nds": "Lüttich", + "vls": "Luuk", + "he": "לייז'", + "li": "Luuk", + "nl": "Luik" + }, + "lat": "50.64514", + "lon": "5.57342", + "id": 1689207163, + "type": "CITY", + "listOfStreets": [ + { + "name": "Rue Émile Gérard", + "lat": "50.64807", + "lon": "5.55378", + "id": 30816 + }, + { + "name": "Rue Émile Zola (Laveu)", + "lat": "50.63302", + "lon": "5.55286", + "id": 163988 + }, + { + "name": "Rue Emile Vandervelde - Rue Sainte-Marguerite", + "lat": "50.64814", + "lon": "5.54915", + "id": 242972 + }, + { + "name": "Place Emile Dupont", + "lat": "50.63685", + "lon": "5.57138", + "id": 209271, + "buildings": [], + "intersectedStreets": [ + { + "name": "Rue Eugène Ysaye", + "lat": "50.63632", + "lon": "5.56998" + }, + { + "name": "Rue Rouveroy", + "lat": "50.63666", + "lon": "5.57127" + }, + { + "name": "Rue du Vertbois", + "lat": "50.63745", + "lon": "5.57084" + } + ] + }, + { + "name": "Rue Émile Verhaeren", + "lat": "50.61385", + "lon": "5.58541", + "id": 72063 + }, + { + "name": "Rue Emile Vandervelde", + "lat": "50.64809", + "lon": "5.55037", + "id": 217495 + }, + { + "name": "Boulevard Emile de Laveleye", + "lat": "50.61904", + "lon": "5.59133", + "id": 32322, + "buildings": [], + "intersectedStreets": [ + { + "name": "Quai des Ardennes", + "lat": "50.61897", + "lon": "5.59133" + }, + { + "name": "Avenue Reine Élisabeth", + "lat": "50.61971", + "lon": "5.59131" + }, + { + "name": "Rue Paul-Joseph Delcloche", + "lat": "50.61846", + "lon": "5.59303" + }, + { + "name": "Rue Stappers", + "lat": "50.62049", + "lon": "5.59120" + }, + { + "name": "Rue de Weltzar", + "lat": "50.62096", + "lon": "5.59110" + }, + { + "name": "Rue de Stavelot", + "lat": "50.62193", + "lon": "5.59058" + }, + { + "name": "Avenue du Luxembourg", + "lat": "50.62242", + "lon": "5.59026" + }, + { + "name": "Quai Mativa", + "lat": "50.62298", + "lon": "5.58108" + }, + { + "name": "Rue des Vennes", + "lat": "50.62388", + "lon": "5.58895" + }, + { + "name": "Rue Joseph Delboeuf", + "lat": "50.62398", + "lon": "5.58846" + }, + { + "name": "Rue de Londres", + "lat": "50.62439", + "lon": "5.58708" + }, + { + "name": "Rue Saint-Vincent (Vennes)", + "lat": "50.62441", + "lon": "5.58614" + }, + { + "name": "Rue de Paris (Vennes)", + "lat": "50.62441", + "lon": "5.58614" + }, + { + "name": "Avenue Albert Mahiels", + "lat": "50.62345", + "lon": "5.58376" + }, + { + "name": "Rue de Verviers (Vennes)", + "lat": "50.62355", + "lon": "5.58391" + }, + { + "name": "Rue de Spa (Vennes)", + "lat": "50.62343", + "lon": "5.58872" + }, + { + "name": "Rue de Fétinne", + "lat": "50.62371", + "lon": "5.58324" + } + ] + }, + { + "name": "Avenue Emile Jenissen", + "lat": "50.64056", + "lon": "5.54822", + "id": 33433, + "intersectedStreets": [ + { + "name": "Rue de Burenville", + "lat": "50.64085", + "lon": "5.54777" + }, + { + "name": "Avenue Olympe Gilbart", + "lat": "50.64056", + "lon": "5.54822" + } + ] + }, + { + "name": "Rue du Couvent - Rue Emile Vandervelde", + "lat": "50.64343", + "lon": "5.65472", + "id": 205563 + }, + { + "name": "Rue Émile Collard", + "lat": "50.62739", + "lon": "5.59498", + "id": 88893 + }, + { + "name": "Avenue Emile Digneffe", + "lat": "50.61993", + "lon": "5.57412", + "id": 188339, + "buildings": [], + "intersectedStreets": [ + { + "name": "Place du Général Leman", + "lat": "50.61993", + "lon": "5.57412" + }, + { + "name": "Rue Ernest Solvay", + "lat": "50.61982", + "lon": "5.57423" + }, + { + "name": "Quai de Rome", + "lat": "50.62054", + "lon": "5.57754" + }, + { + "name": "Rue de Fétinne", + "lat": "50.62054", + "lon": "5.57754" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Grand-Manil", + "names": { + "wa": "Grand-Mayni-dlé-Djiblou", + "nl": "Grand-Manil" + }, + "lat": "50.55692", + "lon": "4.68303", + "id": 301317281, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Somville", + "lat": "50.55672", + "lon": "4.67608", + "id": 66935 + } + ], + "matchStreet": 1 + }, + { + "name": "Limbourg", + "names": { + "de": "Limburg an der Weser", + "uk": "Лімбур", + "wa": "Limbôr", + "it": "Limburgo", + "fr": "Limbourg", + "nl": "Limburg" + }, + "lat": "50.61228", + "lon": "5.94030", + "id": 1690226019, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Colette", + "lat": "50.62151", + "lon": "5.93918", + "id": 104509 + } + ], + "matchStreet": 1 + }, + { + "name": "Seilles", + "names": { + "ru": "Сей", + "wa": "Seye", + "nl": "Seilles" + }, + "lat": "50.49842", + "lon": "5.08055", + "id": 689778689, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Godfrind", + "lat": "50.49403", + "lon": "5.08547", + "id": 238169 + } + ], + "matchStreet": 1 + }, + { + "name": "Leuven", + "enName": "Leuven", + "names": { + "de": "Löwen", + "ru": "Лёвен", + "pt": "Lovaina", + "bg": "Льовен", + "el": "Λέουβεν", + "lt": "Leuvenas", + "it": "Lovanio", + "fr": "Louvain", + "es": "Lovaina", + "zh": "鲁汶", + "cs": "Lovaň", + "eu": "Lovaina", + "ar": "لوفان", + "la": "Lovanium", + "lb": "Léiwen", + "ja": "ルーヴェン", + "sk": "Leuven", + "sr-latn": "Luven", + "he": "לוון", + "ro": "Louvain", + "ca": "Lovaina", + "nl": "Leuven", + "sr": "Левен" + }, + "lat": "50.87920", + "lon": "4.70116", + "id": 1448497912, + "type": "CITY", + "listOfStreets": [ + { + "name": "Emile Mathieustraat", + "lat": "50.86931", + "lon": "4.70114", + "id": 26638, + "buildings": [], + "intersectedStreets": [ + { + "name": "Constantin Meunierstraat (Nieuw Kwartier)", + "lat": "50.86930", + "lon": "4.70097" + }, + { + "name": "De Bayostraat", + "lat": "50.86949", + "lon": "4.70271" + }, + { + "name": "Gerard Vander Lindenstraat", + "lat": "50.86949", + "lon": "4.70271" + }, + { + "name": "J.P. Verhaghenstraat", + "lat": "50.86949", + "lon": "4.70271" + } + ] + }, + { + "name": "Emile Van Arenberghstraat", + "lat": "50.87090", + "lon": "4.70071", + "id": 26647, + "buildings": [], + "intersectedStreets": [ + { + "name": "Hendrik Consciencestraat (Nieuw Kwartier)", + "names": { + "etymology:wikidata": "Q378133 (Nieuw Kwartier)" + }, + "lat": "50.87152", + "lon": "4.70037" + }, + { + "name": "Albert Giraudstraat", + "lat": "50.87090", + "lon": "4.70071" + }, + { + "name": "Constantin Meunierstraat (Nieuw Kwartier)", + "lat": "50.87019", + "lon": "4.70172" + }, + { + "name": "J.P. Verhaghenstraat", + "lat": "50.87019", + "lon": "4.70172" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Frasnes-lez-Buissenal", + "lat": "50.66776", + "lon": "3.61944", + "id": 61961943, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Résidence Émile Lesaffre", + "lat": "50.67330", + "lon": "3.61506", + "id": 104698 + } + ], + "matchStreet": 1 + }, + { + "name": "Vedrin", + "names": { + "ru": "Ведрен", + "wa": "Vedrén", + "fr": "Vedrin" + }, + "lat": "50.50196", + "lon": "4.87424", + "id": 348113363, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Marnach", + "lat": "50.50648", + "lon": "4.86759", + "id": 76931 + } + ], + "matchStreet": 1 + }, + { + "name": "Francorchamps", + "names": { + "ru": "Франкоршан" + }, + "lat": "50.45316", + "lon": "5.95283", + "id": 258600476, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Jamar", + "lat": "50.45480", + "lon": "5.95144", + "id": 222864, + "buildings": [] + }, + { + "name": "Rue Emile Goedert", + "lat": "50.45446", + "lon": "5.95199", + "id": 43728 + } + ], + "matchStreet": 1 + }, + { + "name": "Ways", + "lat": "50.61002", + "lon": "4.46247", + "id": 293583883, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Hecq", + "lat": "50.61221", + "lon": "4.46828", + "id": 30826 + }, + { + "name": "Rue Emile Boucqueau", + "lat": "50.61159", + "lon": "4.46599", + "id": 179592 + }, + { + "name": "Rue Emile François", + "lat": "50.64939", + "lon": "4.48094", + "id": 64148 + }, + { + "name": "Rue Émile Marcq", + "lat": "50.61772", + "lon": "4.44925", + "id": 87923 + } + ], + "matchStreet": 1 + }, + { + "name": "Villers-le-Bouillet", + "names": { + "wa": "Viyé-l'Boulet", + "fr": "Villers-le-Bouillet" + }, + "lat": "50.57503", + "lon": "5.26191", + "id": 734877303, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Docteur Emile Neuville", + "lat": "50.60963", + "lon": "5.24078", + "id": 133166 + } + ], + "matchStreet": 1 + }, + { + "name": "Calonne", + "lat": "50.57832", + "lon": "3.43718", + "id": 668024429, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Royer", + "lat": "50.57686", + "lon": "3.43383", + "id": 81707 + } + ], + "matchStreet": 1 + }, + { + "name": "Aulne", + "lat": "50.36670", + "lon": "4.33252", + "id": 4217285089, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde", + "lat": "50.36683", + "lon": "4.33241", + "id": 13744 + } + ], + "matchStreet": 1 + }, + { + "name": "Woluwe-Saint-Lambert - Sint-Lambrechts-Woluwe", + "names": { + "ru": "Синт-Ламрехтс-Волюве", + "fr": "Woluwe-Saint-Lambert", + "nl": "Sint-Lambrechts-Woluwe" + }, + "lat": "50.84669", + "lon": "4.42848", + "id": 66503288, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Avenue Émile Vandervelde - Émile Vanderveldelaan", + "names": { + "fr": "Avenue Émile Vandervelde", + "nl": "Émile Vanderveldelaan" + }, + "lat": "50.84691", + "lon": "4.44088", + "id": 3453 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Colfontaine", + "names": { + "pcd": "Colfontinne", + "ru": "Кольфонтен", + "fr": "Colfontaine" + }, + "lat": "50.40567", + "lon": "3.85139", + "id": 476285157, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Place Émile Zola", + "lat": "50.41749", + "lon": "3.85938", + "id": 165465 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Mont-Saint-Guibert", + "names": { + "wa": "Mont-Sint-Gubiet", + "fr": "Mont-Saint-Guibert" + }, + "lat": "50.63656", + "lon": "4.61269", + "id": 1684787428, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Francqui", + "lat": "50.65690", + "lon": "4.62376", + "id": 12456, + "buildings": [] + }, + { + "name": "Rue Emile Francqui (Corbais)", + "lat": "50.65896", + "lon": "4.62310", + "id": 14521 + }, + { + "name": "Clos Émile Fabry", + "lat": "50.64845", + "lon": "4.66013", + "id": 81199, + "intersectedStreets": [ + { + "name": "Rue de la Dîme (Corbais)", + "lat": "50.64780", + "lon": "4.65835" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Liers", + "lat": "50.69338", + "lon": "5.56376", + "id": 746914256, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Lerousseau", + "lat": "50.69402", + "lon": "5.56762", + "id": 226389 + } + ], + "matchStreet": 1 + }, + { + "name": "Braine-le-Château", + "names": { + "ru": "Брен-ле-Шато", + "fr": "Braine-le-Château", + "nl": "Kasteelbrakel" + }, + "lat": "50.68279", + "lon": "4.26699", + "id": 253363794, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Schampaert (Wauthier-Braine)", + "lat": "50.68467", + "lon": "4.29962", + "id": 43679 + }, + { + "name": "Rue Emile Vandervelde (Wauthier-Braine)", + "lat": "50.68732", + "lon": "4.30550", + "id": 43682 + } + ], + "matchStreet": 1 + }, + { + "name": "Saint-Servais", + "names": { + "wa": "Sint-Serwai", + "fr": "Saint-Servais" + }, + "lat": "50.47787", + "lon": "4.84525", + "id": 685354387, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Melchior", + "lat": "50.47283", + "lon": "4.83909", + "id": 82158 + } + ], + "matchStreet": 1 + }, + { + "name": "Bernissart", + "names": { + "ru": "Бернисар" + }, + "lat": "50.47550", + "lon": "3.65047", + "id": 476258543, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Cité Emile Carlier", + "lat": "50.48119", + "lon": "3.65941", + "id": 165947 + }, + { + "name": "Rue Emile Carlier (Blaton)", + "lat": "50.49776", + "lon": "3.66885", + "id": 14487 + }, + { + "name": "Place Emile Vandervelde (Harchies)", + "lat": "50.47653", + "lon": "3.68988", + "id": 99894 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Sint-Denijs-Westrem", + "names": { + "ru": "Синт-Денейс-Вестрем" + }, + "lat": "51.01994", + "lon": "3.66767", + "id": 2428597649, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Andelhofstraat", + "lat": "51.02353", + "lon": "3.67094", + "id": 107334 + } + ], + "matchStreet": 1 + }, + { + "name": "Watermael-Boitsfort - Watermaal-Bosvoorde", + "names": { + "ru": "Ватермаль-Буафор", + "fr": "Watermael-Boitsfort", + "nl": "Watermaal-Bosvoorde" + }, + "lat": "50.79939", + "lon": "4.41582", + "id": 66501221, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Avenue Emile Van Becelaere - Emile Van Becelaerelaan", + "names": { + "fr": "Avenue Emile Van Becelaere", + "nl": "Emile Van Becelaerelaan" + }, + "lat": "50.79995", + "lon": "4.40575", + "id": 14039 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Frameries", + "names": { + "ru": "Фрамри" + }, + "lat": "50.40876", + "lon": "3.89055", + "id": 480959936, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Verhaeren (Cité Louis Piérard)", + "lat": "50.39666", + "lon": "3.90251", + "id": 170289 + }, + { + "name": "Rue Émile Vandervelde", + "lat": "50.40457", + "lon": "3.89570", + "id": 170295 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Ixelles - Elsene", + "names": { + "ru": "Иксель", + "fr": "Ixelles", + "nl": "Elsene" + }, + "lat": "50.82229", + "lon": "4.38157", + "id": 66498145, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Avenue Emile Duray - Emile Duraylaan", + "names": { + "fr": "Avenue Émile Duray", + "nl": "Émile Duraylaan" + }, + "lat": "50.81661", + "lon": "4.37644", + "id": 216093 + }, + { + "name": "Rue Émile Banning - Émile Banningstraat", + "names": { + "fr": "Rue Émile Banning", + "nl": "Émile Banningstraat" + }, + "lat": "50.81856", + "lon": "4.38204", + "id": 4408 + }, + { + "name": "Rue Paul Émile Janson - Paul Émile Jansonstraat", + "names": { + "fr": "Rue Paul Émile Janson", + "nl": "Paul Émile Jansonstraat" + }, + "lat": "50.82715", + "lon": "4.35960", + "id": 14656 + }, + { + "name": "Avenue Émile De Mot - Émile De Motlaan", + "names": { + "fr": "Avenue Émile De Mot", + "nl": "Émile De Motlaan" + }, + "lat": "50.81842", + "lon": "4.37434", + "id": 14662 + }, + { + "name": "Avenue Émile de Beco - Émile de Becolaan", + "names": { + "fr": "Avenue Émile de Beco", + "nl": "Émile de Becolaan" + }, + "lat": "50.82363", + "lon": "4.38043", + "id": 4429 + }, + { + "name": "Rue Èmile Bouilliot - Èmile Bouilliotstraat", + "names": { + "fr": "Rue Émile Bouilliot", + "nl": "Émile Bouilliotstraat" + }, + "lat": "50.81831", + "lon": "4.35483", + "id": 247324 + }, + { + "name": "Rue Émile Claus - Émile Clausstraat", + "names": { + "fr": "Rue Émile Claus", + "nl": "Émile Clausstraat" + }, + "lat": "50.81633", + "lon": "4.37001", + "id": 13970 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Sambreville", + "names": { + "ru": "Самбрвиль" + }, + "lat": "50.44098", + "lon": "4.61967", + "id": 482112931, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Duculot", + "lat": "50.43541", + "lon": "4.60887", + "id": 245796 + }, + { + "name": "Rue Émile Vandervelde (Moignelée)", + "lat": "50.44242", + "lon": "4.58203", + "id": 136359 + }, + { + "name": "Rue Émile Zola (Keumiée)", + "lat": "50.46324", + "lon": "4.59050", + "id": 129957 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Vottem", + "names": { + "ru": "Воттем" + }, + "lat": "50.67265", + "lon": "5.58462", + "id": 746914268, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Vandervelde", + "lat": "50.67292", + "lon": "5.58367", + "id": 202077, + "intersectedStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.67294", + "lon": "5.58367" + } + ] + }, + { + "name": "Rue Emile Vandervelde", + "lat": "50.67347", + "lon": "5.58311", + "id": 32523 + } + ], + "matchStreet": 1 + }, + { + "name": "Zaventem", + "names": { + "ru": "Завентем" + }, + "lat": "50.88044", + "lon": "4.47455", + "id": 60166648, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile De Muncklaan", + "lat": "50.87625", + "lon": "4.48416", + "id": 67248 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Sint-Amands", + "names": { + "ru": "Синт-Амандс", + "fr": "Saint-Amand" + }, + "lat": "51.05571", + "lon": "4.20182", + "id": 73769421, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Verhaerenstraat", + "names": { + "old_name": "Palingstraat", + "etymology:wikidata": "Q193680" + }, + "lat": "51.05816", + "lon": "4.20229", + "id": 15140 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Orp-le-Grand", + "names": { + "fr": "Orp-le-Grand" + }, + "lat": "50.70348", + "lon": "4.99129", + "id": 293416149, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Avenue Emile Vandervelde", + "lat": "50.70560", + "lon": "4.99155", + "id": 205965, + "intersectedStreets": [ + { + "name": "Place du 11e Dragons Français", + "lat": "50.70381", + "lon": "4.99071" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Wihogne", + "lat": "50.72774", + "lon": "5.50640", + "id": 305240961, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Grisard", + "lat": "50.72704", + "lon": "5.50870", + "id": 242689 + } + ], + "matchStreet": 1 + }, + { + "name": "Anderlecht", + "names": { + "ru": "Андерлехт", + "uk": "Андерлехт", + "wa": "Anderlek", + "fr": "Anderlecht", + "li": "Anderlech", + "nl": "Anderlecht" + }, + "lat": "50.83814", + "lon": "4.31235", + "id": 66163274, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Hellebaut - Emile Hellebautstraat", + "names": { + "fr": "Rue Emile Hellebaut", + "nl": "Emile Hellebautstraat" + }, + "lat": "50.84036", + "lon": "4.29834", + "id": 6114 + }, + { + "name": "Square Emile Vandervelde - Emile Vanderveldesquare", + "names": { + "language": "fr - nl", + "fr": "Square Emile Vandervelde", + "nl": "Emile Vanderveldesquare" + }, + "lat": "50.83665", + "lon": "4.31763", + "id": 233781 + }, + { + "name": "Square Émile Vandder Bruggen - Emile Vander Bruggensquare", + "names": { + "fr": "Square Émile Vandder Bruggen", + "nl": "Emile Vander Bruggensquare" + }, + "lat": "50.83875", + "lon": "4.31291", + "id": 31149 + }, + { + "name": "Avenue Émile Gryson - Emile Grysonlaan", + "names": { + "fr": "Avenue Émile Gryson", + "nl": "Emile Grysonlaan" + }, + "lat": "50.81513", + "lon": "4.29417", + "id": 96046 + }, + { + "name": "Rue Emile Versé - Emile Verséstraat", + "names": { + "fr": "Rue Emile Versé", + "nl": "Emile Verséstraat" + }, + "lat": "50.83474", + "lon": "4.29713", + "id": 4959 + }, + { + "name": "Rue Émile Carpentier - Emile Carpentierstraat", + "names": { + "fr": "Rue Émile Carpentier", + "nl": "Emile Carpentierstraat" + }, + "lat": "50.83736", + "lon": "4.31909", + "id": 35758 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Geer", + "names": { + "ru": "Жер" + }, + "lat": "50.66849", + "lon": "5.17106", + "id": 351518028, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Lejeune", + "lat": "50.66932", + "lon": "5.17327", + "id": 229594 + } + ], + "matchStreet": 1 + }, + { + "name": "Saint-Georges-sur-Meuse", + "names": { + "wa": "Sint-Djôr-so-Mouze", + "fr": "Saint-Georges-sur-Meuse" + }, + "lat": "50.60007", + "lon": "5.35789", + "id": 258889426, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Delcour", + "lat": "50.59882", + "lon": "5.37440", + "id": 30543 + } + ], + "matchStreet": 1 + }, + { + "name": "Saint-Sauveur", + "lat": "50.70618", + "lon": "3.59774", + "id": 672610712, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Deltenre", + "lat": "50.70835", + "lon": "3.59772", + "id": 66048 + } + ], + "matchStreet": 1 + }, + { + "name": "Gerpinnes", + "names": { + "ru": "Жерпинн" + }, + "lat": "50.33782", + "lon": "4.52768", + "id": 331350597, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Genard", + "lat": "50.33990", + "lon": "4.53188", + "id": 145060 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Cité de Saint-Vaast", + "lat": "50.46528", + "lon": "4.16659", + "id": 4543339338, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Rue Émile Urbain", + "lat": "50.46545", + "lon": "4.16828", + "id": 238033 + } + ], + "matchStreet": 1 + }, + { + "name": "Gives", + "lat": "50.50750", + "lon": "5.15452", + "id": 317234977, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.50630", + "lon": "5.15383", + "id": 75651 + } + ], + "matchStreet": 1 + }, + { + "name": "Overijse", + "names": { + "ru": "Оверейсе" + }, + "lat": "50.77292", + "lon": "4.53851", + "id": 362632215, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Carelsstraat", + "lat": "50.77114", + "lon": "4.54023", + "id": 98877, + "buildings": [], + "intersectedStreets": [ + { + "name": "Terhulpensesteenweg", + "lat": "50.77128", + "lon": "4.54107" + }, + { + "name": "Jan-Baptist Dekeyserstraat", + "lat": "50.77109", + "lon": "4.53997" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Gozée", + "names": { + "ru": "Гозе" + }, + "lat": "50.33323", + "lon": "4.35155", + "id": 671657349, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde (Aulne)", + "lat": "50.36585", + "lon": "4.33003", + "id": 24229 + } + ], + "matchStreet": 1 + }, + { + "name": "Groot-Bijgaarden", + "names": { + "fr": "Grand-Bigard" + }, + "lat": "50.87071", + "lon": "4.26514", + "id": 251470642, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile de Blutslaan", + "lat": "50.86510", + "lon": "4.26488", + "id": 58171 + } + ], + "matchStreet": 1 + }, + { + "name": "Roux", + "lat": "50.44278", + "lon": "4.39485", + "id": 335460915, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Saint Emile", + "lat": "50.42680", + "lon": "4.40307", + "id": 222517 + }, + { + "name": "Rue Émile Vandervelde", + "lat": "50.43981", + "lon": "4.37539", + "id": 149938 + } + ], + "matchStreet": 1 + }, + { + "name": "Waremme", + "names": { + "ru": "Варем", + "wa": "Wareme", + "fr": "Waremme", + "nl": "Borgworm" + }, + "lat": "50.69769", + "lon": "5.25462", + "id": 79454553, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Avenue Emile Vandervelde", + "lat": "50.69662", + "lon": "5.25754", + "id": 69790, + "intersectedStreets": [ + { + "name": "Avenue Guillaume Joachim", + "lat": "50.69662", + "lon": "5.25754" + }, + { + "name": "Rue Ernest Malvoz", + "lat": "50.69566", + "lon": "5.25889" + }, + { + "name": "Rue du Tram", + "lat": "50.69540", + "lon": "5.25953" + }, + { + "name": "Chemin des Hirondelles", + "lat": "50.69540", + "lon": "5.25953" + }, + { + "name": "Rue du Fond d'Or", + "lat": "50.69389", + "lon": "5.26340" + } + ] + }, + { + "name": "Rue Emile Marchoul", + "lat": "50.67932", + "lon": "5.23426", + "id": 142039 + }, + { + "name": "Rue Emile Hallet", + "lat": "50.69644", + "lon": "5.25142", + "id": 151281 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Niel", + "names": { + "ru": "Нил" + }, + "lat": "51.10987", + "lon": "4.33033", + "id": 262508108, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Vanderveldelaan", + "lat": "51.10700", + "lon": "4.32760", + "id": 204779 + }, + { + "name": "Emile Vinckstraat", + "lat": "51.11018", + "lon": "4.33492", + "id": 61081 + } + ], + "matchStreet": 1 + }, + { + "name": "Koekelberg", + "names": { + "ar": "كوكلبرغ", + "ru": "Кукельберг" + }, + "lat": "50.86226", + "lon": "4.32572", + "id": 66192903, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Sergijsels - Emile Sergijselsstraat", + "names": { + "fr": "Rue Emile Sergijsels", + "nl": "Emile Sergijselsstraat" + }, + "lat": "50.86073", + "lon": "4.33230", + "id": 4708 + }, + { + "name": "Avenue Émile Bossaert - Émile Bossaertlaan", + "names": { + "fr": "Avenue Émile Bossaert", + "nl": "Émile Bossaertlaan" + }, + "lat": "50.86502", + "lon": "4.31844", + "id": 4752 + }, + { + "name": "Rue Emile Deroover - Emile Derooverstraat", + "names": { + "fr": "Rue Emile Deroover", + "nl": "Emile Derooverstraat" + }, + "lat": "50.86513", + "lon": "4.31518", + "id": 4771 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Marchin", + "names": { + "ru": "Маршен", + "wa": "Mårcin", + "fr": "Marchin" + }, + "lat": "50.48020", + "lon": "5.22634", + "id": 1884725616, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.47648", + "lon": "5.21368", + "id": 122172 + } + ], + "matchStreet": 1 + }, + { + "name": "Ottignies-Louvain-la-Neuve", + "names": { + "ru": "Оттиньи-Лувен-ла-Нёв" + }, + "lat": "50.66542", + "lon": "4.56742", + "id": 292453525, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Goes (Louvain-la-Neuve)", + "lat": "50.66461", + "lon": "4.61765", + "id": 94547 + }, + { + "name": "Avenue Émile Verhaeren (Louvain-la-Neuve)", + "lat": "50.66147", + "lon": "4.60889", + "id": 94577, + "intersectedStreets": [ + { + "name": "Avenue des Arts (Louvain-la-Neuve)", + "lat": "50.66256", + "lon": "4.60750" + }, + { + "name": "Rue Victor Horta (Louvain-la-Neuve)", + "lat": "50.66256", + "lon": "4.60750" + }, + { + "name": "Rue Achille Chavée (Louvain-la-Neuve)", + "lat": "50.66187", + "lon": "4.60840" + }, + { + "name": "Rue Marie Gevers (Louvain-la-Neuve)", + "lat": "50.66187", + "lon": "4.60840" + }, + { + "name": "Rue Albert Mockel (Louvain-la-Neuve)", + "lat": "50.66140", + "lon": "4.60900" + }, + { + "name": "Parvis de la Cantilène (Louvain-la-Neuve)", + "lat": "50.66160", + "lon": "4.61001" + } + ] + }, + { + "name": "Rue Émile Mathéi", + "lat": "50.68482", + "lon": "4.56971", + "id": 64255 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Gosselies", + "names": { + "ru": "Госли", + "ro": "cazare" + }, + "lat": "50.46539", + "lon": "4.43017", + "id": 335461892, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Place Émile Bertaux", + "lat": "50.46686", + "lon": "4.43161", + "id": 208149 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Pepinster", + "names": { + "wa": "Pepinster", + "fr": "Pepinster" + }, + "lat": "50.56752", + "lon": "5.80370", + "id": 534717923, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Wegnez)", + "lat": "50.57757", + "lon": "5.82593", + "id": 74909 + }, + { + "name": "Rue Émile Fairon", + "lat": "50.56534", + "lon": "5.80997", + "id": 117609 + } + ], + "matchStreet": 1 + }, + { + "name": "Berloz", + "names": { + "wa": "Bierlô", + "fr": "Berloz" + }, + "lat": "50.69774", + "lon": "5.21462", + "id": 734838286, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Muselle", + "lat": "50.70177", + "lon": "5.21074", + "id": 151433 + } + ], + "matchStreet": 1 + }, + { + "name": "Herstal", + "names": { + "ru": "Эрсталь", + "wa": "Hèsta", + "fr": "Herstal" + }, + "lat": "50.67019", + "lon": "5.64039", + "id": 1933469358, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Place Emile Vandervelde (Vottem)", + "lat": "50.67292", + "lon": "5.58369", + "id": 202076, + "intersectedStreets": [ + { + "name": "Rue Emile Vandervelde (Vottem)", + "lat": "50.67294", + "lon": "5.58369" + } + ] + }, + { + "name": "Rue Emile Tilman", + "lat": "50.66988", + "lon": "5.63575", + "id": 136537 + }, + { + "name": "Rue Emile Muraille", + "lat": "50.67570", + "lon": "5.61944", + "id": 19991 + }, + { + "name": "Rue Emile Vinck", + "lat": "50.67687", + "lon": "5.61777", + "id": 117284 + }, + { + "name": "Rue Émile Lerousseau (Liers)", + "lat": "50.69761", + "lon": "5.56833", + "id": 32506 + }, + { + "name": "Rue Emile Vandervelde (Vottem)", + "lat": "50.67348", + "lon": "5.58314", + "id": 32522 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Fléron", + "names": { + "ru": "Флерон", + "wa": "Fléron", + "fr": "Fléron" + }, + "lat": "50.61684", + "lon": "5.68321", + "id": 358184078, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandevelde", + "lat": "50.61052", + "lon": "5.66495", + "id": 64913 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Oostende", + "enName": "Ostend", + "names": { + "de": "Ostende", + "ru": "Остенде", + "uk": "Остенде", + "lt": "Ostendė", + "it": "Ostenda", + "fr": "Ostende", + "pl": "Ostenda", + "vls": "Ostende", + "he": "אוסטנדה", + "es": "Ostende", + "nl": "Oostende" + }, + "lat": "51.23032", + "lon": "2.92032", + "id": 1651555409, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Roosestraat", + "lat": "51.20905", + "lon": "2.89129", + "id": 25720 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Knokke", + "names": { + "ru": "Кнокке", + "fr": "Knocke", + "vls": "Knokke-Heist" + }, + "lat": "51.34645", + "lon": "3.28764", + "id": 1395162377, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Verhaerenlaan (Het Zoute)", + "lat": "51.35296", + "lon": "3.30388", + "id": 162982 + }, + { + "name": "Emile Raespad", + "lat": "51.33845", + "lon": "3.28395", + "id": 222497 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Borlez", + "lat": "50.63298", + "lon": "5.24507", + "id": 734856622, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde", + "lat": "50.63138", + "lon": "5.24486", + "id": 149459 + } + ], + "matchStreet": 1 + }, + { + "name": "Antoing", + "names": { + "ru": "Антуан" + }, + "lat": "50.56591", + "lon": "3.45132", + "id": 1766146864, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Vandervelde (Fontenoy)", + "lat": "50.56195", + "lon": "3.47696", + "id": 236156 + }, + { + "name": "Rue Emile Royer (Calonne)", + "lat": "50.57686", + "lon": "3.43383", + "id": 81708 + } + ], + "matchStreet": 1 + }, + { + "name": "Les Bons Villers", + "names": { + "ru": "Ле-Бон-Виллер" + }, + "lat": "50.52264", + "lon": "4.47635", + "id": 138133013, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Gossiaux (Villers-Perwin)", + "lat": "50.52373", + "lon": "4.47738", + "id": 182271 + }, + { + "name": "Avenue Emile Stassart", + "lat": "50.50304", + "lon": "4.46519", + "id": 40547 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Jumet", + "lat": "50.44213", + "lon": "4.43502", + "id": 2603357469, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Ruiters", + "lat": "50.45205", + "lon": "4.41494", + "id": 160226 + }, + { + "name": "Rue Émile Strimelle", + "lat": "50.43507", + "lon": "4.42573", + "id": 160335 + } + ], + "matchStreet": 1 + }, + { + "name": "Mellet", + "names": { + "ru": "Мелле" + }, + "lat": "50.50412", + "lon": "4.47626", + "id": 138138122, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Avenue Emile Stassart", + "lat": "50.50303", + "lon": "4.46519", + "id": 40548 + } + ], + "matchStreet": 1 + }, + { + "name": "Villers-Perwin", + "lat": "50.52620", + "lon": "4.47839", + "id": 138133008, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Gossiaux (Les Bons Villers)", + "lat": "50.52373", + "lon": "4.47740", + "id": 182272 + } + ], + "matchStreet": 1 + }, + { + "name": "Namur", + "enName": "Namur", + "names": { + "de": "Namur", + "ru": "Намюр", + "lt": "Namiūras", + "eo": "Namuro", + "wa": "Nameur", + "it": "Namur", + "fr": "Namur", + "es": "Namur", + "zh": "那慕爾", + "ar": "نامور", + "la": "Namurcum", + "lb": "Namouer", + "uk": "Намюр", + "nl": "Namen", + "sr": "Намир" + }, + "lat": "50.46653", + "lon": "4.86619", + "id": 1765500676, + "type": "CITY", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Flawinne)", + "lat": "50.45842", + "lon": "4.80437", + "id": 157799 + }, + { + "name": "Rue Emile Marnach", + "lat": "50.50648", + "lon": "4.86759", + "id": 76930 + }, + { + "name": "Rue Emile Dessenius", + "lat": "50.46167", + "lon": "4.81248", + "id": 72888 + }, + { + "name": "Rue Emile Melchior", + "lat": "50.47283", + "lon": "4.83909", + "id": 82157 + }, + { + "name": "Rue Emile Mazy (Flawinne)", + "lat": "50.45895", + "lon": "4.79390", + "id": 147807 + }, + { + "name": "Rue Émile Cuvelier", + "names": { + "etymology:wikidata": "Q2997972" + }, + "lat": "50.46492", + "lon": "4.86514", + "id": 10017 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Wattrelos", + "lat": "50.70086", + "lon": "3.22226", + "id": 26692462, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Allée Emile Verhaeren", + "lat": "50.70615", + "lon": "3.24101", + "id": 226514 + }, + { + "name": "Rangée Émile Brelle", + "lat": "50.71548", + "lon": "3.20790", + "id": 137707 + }, + { + "name": "Rue Émile Basly", + "lat": "50.70266", + "lon": "3.21674", + "id": 112449 + }, + { + "name": "Rue Émile Zola", + "lat": "50.71085", + "lon": "3.19385", + "id": 203632 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Estinnes", + "lat": "50.39767", + "lon": "4.09758", + "id": 477197902, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Heulers", + "lat": "50.34939", + "lon": "4.11794", + "id": 162196 + } + ], + "matchStreet": 1 + }, + { + "name": "Marche-en-Famenne", + "names": { + "ru": "Марш-ан-Фамен", + "wa": "Måtche-el-Famene", + "fr": "Marche-en-Famenne" + }, + "lat": "50.22408", + "lon": "5.34287", + "id": 30103479, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Herman", + "lat": "50.16806", + "lon": "5.28498", + "id": 156848 + }, + { + "name": "Rue Émile Demelenne", + "lat": "50.23417", + "lon": "5.34292", + "id": 113077 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Mazy", + "names": { + "ru": "Мази", + "wa": "Mazi", + "fr": "Mazy", + "nl": "Mazy" + }, + "lat": "50.51276", + "lon": "4.67451", + "id": 61146471, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Pirson", + "lat": "50.50823", + "lon": "4.67378", + "id": 32208 + } + ], + "matchStreet": 1 + }, + { + "name": "Le Rœulx", + "names": { + "ru": "Рё" + }, + "lat": "50.50340", + "lon": "4.11223", + "id": 77821552, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Mignault)", + "lat": "50.53572", + "lon": "4.16749", + "id": 95629 + }, + { + "name": "Rue Emile Vandervelde", + "lat": "50.50345", + "lon": "4.10972", + "id": 97740 + } + ], + "matchStreet": 1 + }, + { + "name": "Péruwelz", + "names": { + "ru": "Перювельз" + }, + "lat": "50.50979", + "lon": "3.59088", + "id": 79427177, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Baijot", + "lat": "50.49678", + "lon": "3.60371", + "id": 158665 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Flawinne", + "names": { + "ru": "Флавин", + "wa": "Flawene", + "fr": "Flawinne" + }, + "lat": "50.45660", + "lon": "4.81208", + "id": 685352654, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.45842", + "lon": "4.80435", + "id": 157800 + }, + { + "name": "Rue Emile Dessenius", + "lat": "50.46166", + "lon": "4.81248", + "id": 72889 + }, + { + "name": "Rue Emile Mazy", + "lat": "50.45895", + "lon": "4.79388", + "id": 147808 + } + ], + "matchStreet": 1 + }, + { + "name": "Comblain-au-Pont", + "names": { + "wa": "Comblin-å-Pont", + "fr": "Comblain-au-Pont" + }, + "lat": "50.47570", + "lon": "5.57485", + "id": 611164729, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.47286", + "lon": "5.57914", + "id": 163087 + } + ], + "matchStreet": 1 + }, + { + "name": "Goutroux", + "lat": "50.41286", + "lon": "4.35754", + "id": 672056852, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Moureau", + "lat": "50.41287", + "lon": "4.35713", + "id": 204394 + } + ], + "matchStreet": 1 + }, + { + "name": "Uccle - Ukkel", + "names": { + "ru": "Юккел-Юкль", + "fr": "Uccle", + "nl": "Ukkel" + }, + "lat": "50.80182", + "lon": "4.33724", + "id": 66199982, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Regard - Emile Regardstraat", + "names": { + "fr": "Rue Emile Regard", + "nl": "Emile Regardstraat" + }, + "lat": "50.81250", + "lon": "4.33823", + "id": 14572 + }, + { + "name": "Rue Émile Claus - Émile Clausstraat", + "names": { + "fr": "Rue Émile Claus", + "nl": "Émile Clausstraat" + }, + "lat": "50.81563", + "lon": "4.36741", + "id": 14658 + }, + { + "name": "Rue Emile Lecomte - Emile Lecomtestraat", + "names": { + "fr": "Rue Emile Lecomte", + "nl": "Emile Lecomtestraat" + }, + "lat": "50.81137", + "lon": "4.34413", + "id": 13867 + }, + { + "name": "Place Emile Danco - Emile Dancoplein", + "names": { + "fr": "Place Émile Danco", + "nl": "Émile Dancoplein" + }, + "lat": "50.80058", + "lon": "4.33775", + "id": 14910 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Isnes", + "names": { + "wa": "Les Înes", + "fr": "Isnes", + "nl": "Isnes" + }, + "lat": "50.51052", + "lon": "4.74069", + "id": 310110233, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Marchal", + "lat": "50.50341", + "lon": "4.71610", + "id": 221783 + } + ], + "matchStreet": 1 + }, + { + "name": "Brugge", + "enName": "Bruges", + "names": { + "de": "Brügge", + "ru": "Брюгге", + "sv": "Brygge", + "pt": "Bruges", + "el": "Βρύγη", + "lt": "Briugė", + "eo": "Bruĝo", + "lv": "Brige", + "it": "Bruges", + "fr": "Bruges", + "es": "Brujas", + "zh": "布魯日", + "cs": "Bruggy", + "ar": "بروج", + "la": "Brugae", + "lb": "Bruges", + "sk": "Bruggy", + "pl": "Brugia", + "ca": "Bruges", + "nl": "Brugge", + "tr": "Brüj", + "sr": "Бриж" + }, + "lat": "51.20855", + "lon": "3.22676", + "id": 1651558422, + "type": "CITY", + "listOfStreets": [ + { + "name": "Emile Bethuynelaan", + "lat": "51.16468", + "lon": "3.18908", + "id": 205073 + }, + { + "name": "Emile en Dora Rommelaerestraat", + "lat": "51.22929", + "lon": "3.24313", + "id": 132716 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Bruxelles - Brussel", + "enName": "Brussels", + "names": { + "pt": "Bruxelas", + "hr": "Brisel", + "ht": "Briksèl", + "hu": "Brüsszel", + "yi": "בריסל", + "hy": "Բրյուսել", + "nah": "Brusel", + "pms": "Brussel", + "ia": "Brussel", + "id": "Brussel", + "nap": "Bruxelles", + "scn": "Bruxelles", + "ext": "Brusselas", + "qu": "Brussel", + "af": "Brussel", + "frp": "Brussèles", + "pnb": "برسلز", + "io": "Bruxel", + "is": "Brussel", + "it": "Bruxelles", + "am": "ብሩክሴል", + "an": "Bruselas", + "zh": "布魯塞爾", + "ar": "بروكسل", + "stq": "Brussel", + "ja": "ブリュッセル", + "zh-min-nan": "Brussels", + "ro": "Bruxelles", + "be": "Брусель", + "ru": "Брюссель", + "bg": "Брюксел", + "jv": "Brussel", + "bo": "པུའུ་ལུའུ་སེལ​།", + "br": "Brusel", + "bs": "Brisel", + "arc": "ܒܪܘܟܣܠ", + "sh": "Bruxelles", + "ka": "ბრიუსელი", + "wuu": "布鲁塞尔", + "sk": "Brusel", + "sl": "Bruselj", + "ca": "Brussel·les", + "sq": "Brukseli", + "sr": "Брисел", + "kk": "Бруссел", + "kl": "Bruxelles", + "ce": "Бруссель", + "kn": "ಬ್ರಸೆಲ್ಸ್", + "sv": "Bryssel", + "ko": "브뤼셀", + "sw": "Brussels", + "ku": "Bruksel", + "kv": "Брюссель", + "kw": "Brusselas", + "tpi": "Brussels", + "ta": "பிரசெல்சு", + "cs": "Brusel", + "tg": "Брюссел", + "th": "บรัสเซลส์", + "la": "Bruxellae", + "cy": "Brwsel", + "lb": "Bréissel", + "nds": "Brüssel", + "vls": "Brussel", + "da": "Bruxelles", + "li": "Brussel", + "tr": "Brüksel", + "be-tarask": "Брусэль", + "de": "Brüssel", + "ln": "Bruxelles", + "ast": "Bruxeles", + "rue": "Брусел", + "lt": "Briuselis", + "lv": "Brisele", + "simple": "Brussels", + "lij": "Bruxelles", + "diq": "Bruksel", + "lad": "Bruselas", + "ug": "Bryussél", + "vec": "Borseła", + "uk": "Брюссель", + "zea": "Brussel", + "mg": "Brussel", + "mi": "Parahara", + "ur": "برسلز", + "mk": "Брисел", + "pap": "Brusela", + "vep": "Brüssel'", + "mn": "Брюссель", + "mr": "ब्रसेल्स", + "ms": "Brussels", + "el": "Βρυξέλλες", + "mt": "Brussell", + "tzl": "Brüxell", + "eo": "Bruselo", + "my": "ဘရပ်ဆဲလ်မြို့", + "es": "Bruselas", + "et": "Brüssel", + "eu": "Brusela", + "na": "Brussels", + "vi": "Bruxelles", + "bat-smg": "Briuselis", + "fa": "بروکسل", + "nl": "Brussel", + "nn": "Brussel", + "no": "Brussel", + "fi": "Bryssel", + "yue": "布魯塞爾", + "fo": "Brussel", + "wa": "Brussele", + "fr": "Bruxelles", + "kab": "Bruxelles", + "fy": "Brussel", + "nov": "Bruxelles", + "oc": "Brussèlas", + "crh": "Brüksel", + "ga": "An Bhruiséil", + "ang": "Brysel", + "sah": "Брүссель", + "gd": "A' Bhruiseal", + "os": "Брюссель", + "szl": "Bruksela", + "gl": "Bruxelas", + "gv": "Yn Vrussyl", + "ckb": "برۆکسل", + "pl": "Bruksela", + "sr-latn": "Brisel", + "he": "בריסל" + }, + "lat": "50.84656", + "lon": "4.35170", + "id": 1635651356, + "type": "CITY", + "listOfStreets": [ + { + "name": "Rue Paul Émile Janson - Paul Émile Jansonstraat", + "names": { + "fr": "Rue Paul Émile Janson", + "nl": "Paul Émile Jansonstraat" + }, + "lat": "50.82820", + "lon": "4.36217", + "id": 14608 + }, + { + "name": "Boulevard Emile Jacqmain - Emile Jacqmainlaan", + "names": { + "old_name:fr": "Boulevard de la Senne", + "old_name": "Boulevard de la Senne - Zennelaan", + "old_name:nl": "Zennelaan", + "fr": "Boulevard Emile Jacqmain", + "nl": "Emile Jacqmainlaan" + }, + "lat": "50.85455", + "lon": "4.35417", + "id": 14844 + }, + { + "name": "Avenue Emile Van Ermengem - Emile Van Ermengemlaan", + "lat": "50.89206", + "lon": "4.33132", + "id": 5934 + }, + { + "name": "Rue Emile Wauters - Emile Wautersstraat", + "lat": "50.88702", + "lon": "4.34290", + "id": 5985 + }, + { + "name": "Boulevard Émile Bockstael - Emile Bockstaellaan", + "names": { + "fr": "Boulevard Émile Bockstael", + "nl": "Emile Bockstaellaan" + }, + "lat": "50.87194", + "lon": "4.34466", + "id": 5992 + }, + { + "name": "Rue Émile Delva - Émile Delvastraat", + "lat": "50.88260", + "lon": "4.34142", + "id": 5999 + }, + { + "name": "Place Émile Bockstael - Emile Bockstaelplein", + "names": { + "fr": "Place Émile Bockstael", + "nl": "Emile Bockstaelplein" + }, + "lat": "50.87761", + "lon": "4.34683", + "id": 6065 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Merbes-le-Château", + "lat": "50.32340", + "lon": "4.16448", + "id": 256130286, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Fontaine-Valmont)", + "lat": "50.32034", + "lon": "4.21564", + "id": 224568 + }, + { + "name": "Rue Emile Pourbaix", + "lat": "50.31596", + "lon": "4.21682", + "id": 224570 + } + ], + "matchStreet": 1 + }, + { + "name": "Ernage", + "names": { + "wa": "Ernadje", + "nl": "Ernage" + }, + "lat": "50.59420", + "lon": "4.67400", + "id": 365967231, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Labarre", + "lat": "50.59404", + "lon": "4.66848", + "id": 62537 + } + ], + "matchStreet": 1 + }, + { + "name": "Diest", + "names": { + "ru": "Дист" + }, + "lat": "50.98451", + "lon": "5.05034", + "id": 64670063, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Vanderveldestraat", + "lat": "50.97764", + "lon": "5.05472", + "id": 124265, + "buildings": [], + "intersectedStreets": [ + { + "name": "Kloosterbergstraat", + "lat": "50.97747", + "lon": "5.05448" + }, + { + "name": "Montgommerystraat", + "lat": "50.97826", + "lon": "5.05523" + }, + { + "name": "Bloemenlaan", + "lat": "50.97871", + "lon": "5.05157" + }, + { + "name": "Speelhofstraat", + "lat": "50.97899", + "lon": "5.05032" + }, + { + "name": "Frankenstraat", + "lat": "50.97952", + "lon": "5.04961" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Pironchamps", + "lat": "50.42860", + "lon": "4.52605", + "id": 449218688, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde", + "lat": "50.42859", + "lon": "4.52431", + "id": 236378 + } + ], + "matchStreet": 1 + }, + { + "name": "Jeuk", + "names": { + "ru": "Йёк", + "fr": "Goyer", + "nl": "Jeuk" + }, + "lat": "50.73424", + "lon": "5.20969", + "id": 1484663298, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Beauduinstraat (Klein-Jeuk)", + "lat": "50.71964", + "lon": "5.18898", + "id": 201150, + "intersectedStreets": [ + { + "name": "Kasteelstraat (Klein-Jeuk)", + "lat": "50.72168", + "lon": "5.18810" + }, + { + "name": "Spoorwegstraat (Klein-Jeuk)", + "lat": "50.72168", + "lon": "5.18810" + }, + { + "name": "Kustrijkstraat (Klein-Jeuk)", + "lat": "50.72407", + "lon": "5.18711" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Dinant", + "names": { + "ru": "Динан" + }, + "lat": "50.26081", + "lon": "4.91241", + "id": 2850062184, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Wauthy", + "lat": "50.23241", + "lon": "4.90563", + "id": 242396 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Leernes", + "lat": "50.39744", + "lon": "4.33078", + "id": 427112190, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Marcq", + "lat": "50.39730", + "lon": "4.33018", + "id": 173807 + } + ], + "matchStreet": 1 + }, + { + "name": "Marcinelle", + "lat": "50.39773", + "lon": "4.44425", + "id": 674001446, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Avenue Emile Rousseaux", + "lat": "50.40260", + "lon": "4.42674", + "id": 129026 + }, + { + "name": "Square Émile Buisset", + "lat": "50.38316", + "lon": "4.45341", + "id": 130207 + }, + { + "name": "Rue Émile Nonnon", + "lat": "50.40142", + "lon": "4.44266", + "id": 127157 + }, + { + "name": "Rue Émile Vandervelde", + "lat": "50.39180", + "lon": "4.44043", + "id": 140478 + }, + { + "name": "Rue Émile Cliquet", + "lat": "50.39904", + "lon": "4.44820", + "id": 127182 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Jette", + "names": { + "ru": "Жет", + "fr": "Jette", + "nl": "Jette" + }, + "lat": "50.87776", + "lon": "4.32608", + "id": 66513277, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Delva - Émile Delvastraat", + "names": { + "fr": "Rue Émile Delva", + "nl": "Émile Delvastraat" + }, + "lat": "50.87861", + "lon": "4.34252", + "id": 52212 + }, + { + "name": "Avenue Emile Van Ermengem - Emile Van Ermengemlaan", + "names": { + "fr": "Avenue Emile Van Ermengem", + "nl": "Emile Van Ermengemlaan" + }, + "lat": "50.89294", + "lon": "4.32524", + "id": 2586 + }, + { + "name": "Rue Emile Wauters - Emile Wautersstraat", + "names": { + "fr": "Rue Emile Wauters", + "nl": "Emile Wautersstraat" + }, + "lat": "50.88702", + "lon": "4.34288", + "id": 70372 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Manage", + "names": { + "ru": "Манаж" + }, + "lat": "50.50402", + "lon": "4.23555", + "id": 673954341, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Petit-Bois-d'Haine)", + "lat": "50.48205", + "lon": "4.21242", + "id": 158850 + }, + { + "name": "Avenue Emile Herman", + "lat": "50.48448", + "lon": "4.22750", + "id": 98455 + }, + { + "name": "Rue Emile Rousseau", + "lat": "50.48409", + "lon": "4.23098", + "id": 184764 + }, + { + "name": "Chaussée de Jolimont - Avenue Emile Herman", + "lat": "50.47769", + "lon": "4.22336", + "id": 245605 + } + ], + "matchStreet": 1 + }, + { + "name": "Stambruges", + "names": { + "ru": "Стамбрюж" + }, + "lat": "50.50835", + "lon": "3.72025", + "id": 203844147, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Paul-Emile Janson", + "lat": "50.50981", + "lon": "3.71883", + "id": 194950 + } + ], + "matchStreet": 1 + }, + { + "name": "Couillet", + "names": { + "ru": "Куйе" + }, + "lat": "50.39122", + "lon": "4.46862", + "id": 673999413, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde", + "lat": "50.39197", + "lon": "4.46837", + "id": 165791 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Blaton", + "lat": "50.50117", + "lon": "3.66117", + "id": 506142776, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Carlier", + "lat": "50.50109", + "lon": "3.66302", + "id": 194078 + } + ], + "matchStreet": 1 + }, + { + "name": "Gedinne", + "names": { + "ru": "Жедин", + "wa": "Djedene", + "fr": "Gedinne", + "nl": "Gedinne" + }, + "lat": "49.97927", + "lon": "4.93889", + "id": 73813704, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Montreuil", + "lat": "49.99511", + "lon": "4.88739", + "id": 128056 + } + ], + "matchStreet": 1 + }, + { + "name": "La Bifurcation", + "lat": "50.47777", + "lon": "4.22366", + "id": 1799772584, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Chaussée de Jolimont - Avenue Emile Herman", + "lat": "50.47769", + "lon": "4.22336", + "id": 245606 + } + ], + "matchStreet": 1 + }, + { + "name": "Hotton", + "names": { + "ru": "Оттон", + "wa": "Houton", + "fr": "Hotton" + }, + "lat": "50.26880", + "lon": "5.44632", + "id": 292660179, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Parfonry", + "lat": "50.26187", + "lon": "5.43482", + "id": 93151 + } + ], + "matchStreet": 1 + }, + { + "name": "Perwez", + "names": { + "ru": "Первез", + "wa": "Perwé", + "fr": "Perwez", + "nl": "Perwijs" + }, + "lat": "50.62345", + "lon": "4.81347", + "id": 571286635, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Crebeyck", + "lat": "50.62351", + "lon": "4.81705", + "id": 159822 + }, + { + "name": "Rue Emile Masset", + "lat": "50.66123", + "lon": "4.82128", + "id": 90345 + }, + { + "name": "Rue Emile de Brabant", + "lat": "50.62409", + "lon": "4.81448", + "id": 128923 + } + ], + "matchStreet": 1 + }, + { + "name": "Maret", + "lat": "50.71458", + "lon": "4.99367", + "id": 2084148886, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Avenue Emile Vandervelde", + "lat": "50.71393", + "lon": "4.99307", + "id": 135837, + "intersectedStreets": [ + { + "name": "Place de Maret", + "lat": "50.71423", + "lon": "4.99305" + }, + { + "name": "Rue Eugène Malevé", + "lat": "50.71423", + "lon": "4.99305" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Chapelle-lez-Herlaimont", + "names": { + "ru": "Шапель-лез-Эрлемон" + }, + "lat": "50.47133", + "lon": "4.28042", + "id": 348312112, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Duhoux", + "lat": "50.44041", + "lon": "4.30098", + "id": 143585 + }, + { + "name": "Rue Emile Vandervelde", + "lat": "50.47635", + "lon": "4.28714", + "id": 199968 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Pousset", + "lat": "50.69564", + "lon": "5.30346", + "id": 734893372, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Dans", + "lat": "50.69519", + "lon": "5.30292", + "id": 242744, + "intersectedStreets": [ + { + "name": "Rue de l'Arbre", + "lat": "50.69526", + "lon": "5.30311" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Couvin", + "names": { + "ru": "Кувен", + "wa": "Couvén", + "nl": "Couvin" + }, + "lat": "50.05249", + "lon": "4.49551", + "id": 60561637, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Résidence Emile Donnay", + "lat": "50.04311", + "lon": "4.50246", + "id": 107449 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Mettet", + "names": { + "ru": "Мете", + "wa": "Metet", + "nl": "Mettet" + }, + "lat": "50.32121", + "lon": "4.65850", + "id": 252141952, + "type": "TOWN", + "listOfStreets": [ + { + "name": "rue Emile de Clercq", + "lat": "50.27572", + "lon": "4.65722", + "id": 139160 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Dolhain", + "lat": "50.61971", + "lon": "5.94126", + "id": 207241892, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Colette", + "lat": "50.62151", + "lon": "5.93918", + "id": 104510 + } + ], + "matchStreet": 1 + }, + { + "name": "Remicourt", + "names": { + "ru": "Ремикур", + "wa": "Remicoû", + "fr": "Remicourt" + }, + "lat": "50.68072", + "lon": "5.32672", + "id": 734893368, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Dans", + "lat": "50.69519", + "lon": "5.30292", + "id": 242743, + "intersectedStreets": [ + { + "name": "Rue de l'Arbre (Pousset)", + "lat": "50.69526", + "lon": "5.30311" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Messancy", + "names": { + "de": "Metzig", + "lb": "Miezeg", + "wa": "Messanceye", + "nl": "Messancy" + }, + "lat": "49.59691", + "lon": "5.81694", + "id": 270800544, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Kirsch", + "lat": "49.58565", + "lon": "5.81775", + "id": 225321 + } + ], + "matchStreet": 1 + }, + { + "name": "Rebecq", + "names": { + "fr": "Rebecq" + }, + "lat": "50.66459", + "lon": "4.13554", + "id": 675995339, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Avenue Emile Geerts", + "lat": "50.66048", + "lon": "4.14343", + "id": 69947 + } + ], + "matchStreet": 1 + }, + { + "name": "Comines-Warneton", + "names": { + "pcd": "Comène-Warneuton", + "ru": "Комин-Варнетон", + "fr": "Comines-Warneton", + "nl": "Komen-Waasten" + }, + "lat": "50.76830", + "lon": "3.00013", + "id": 1719301306, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Cauche", + "names": { + "fr": "Emile Cauchestraat" + }, + "lat": "50.75431", + "lon": "2.94603", + "id": 140514 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Rendeux", + "names": { + "ru": "Рандё" + }, + "lat": "50.23305", + "lon": "5.50432", + "id": 697215753, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Dupont", + "lat": "50.23418", + "lon": "5.50254", + "id": 125533 + } + ], + "matchStreet": 1 + }, + { + "name": "Ans", + "names": { + "ru": "Анс" + }, + "lat": "50.66243", + "lon": "5.51906", + "id": 481371644, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Loncin)", + "lat": "50.65930", + "lon": "5.50353", + "id": 69794 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Ciney", + "names": { + "ru": "Сине", + "wa": "Cînè", + "fr": "Ciney", + "nl": "Ciney" + }, + "lat": "50.29495", + "lon": "5.09742", + "id": 64666967, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Impasse Émile Romnée", + "lat": "50.31254", + "lon": "5.06431", + "id": 152600, + "intersectedStreets": [ + { + "name": "Rue d'Yvoir (Halloy)", + "lat": "50.31248", + "lon": "5.06435" + } + ] + }, + { + "name": "Place Emile Vandervelde", + "lat": "50.29112", + "lon": "5.09193", + "id": 213270, + "intersectedStreets": [ + { + "name": "Rue Piervenne", + "lat": "50.29153", + "lon": "5.09161" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Hermalle-sous-Argenteau", + "lat": "50.71054", + "lon": "5.68088", + "id": 747888133, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile de Laveleye", + "lat": "50.70408", + "lon": "5.67433", + "id": 242607 + } + ], + "matchStreet": 1 + }, + { + "name": "Amay", + "names": { + "ru": "Аме", + "wa": "Ama", + "fr": "Amay" + }, + "lat": "50.54978", + "lon": "5.32410", + "id": 258880948, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.54795", + "lon": "5.32107", + "id": 47785 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Leuze-en-Hainaut", + "names": { + "ru": "Лёз-ан-Эно" + }, + "lat": "50.59882", + "lon": "3.61673", + "id": 34078889, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Albot", + "lat": "50.62961", + "lon": "3.65310", + "id": 195230 + }, + { + "name": "Rue Emile Fontaine", + "lat": "50.62438", + "lon": "3.63158", + "id": 104184 + }, + { + "name": "Rue Emile Vandervelde", + "lat": "50.59941", + "lon": "3.61714", + "id": 101230 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Thuin", + "names": { + "ru": "Тюэн" + }, + "lat": "50.33974", + "lon": "4.28705", + "id": 79447645, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Vandervelde (Aulne)", + "lat": "50.36683", + "lon": "4.33241", + "id": 13743 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Ittre", + "names": { + "ru": "Итр", + "fr": "Ittre", + "nl": "Itter" + }, + "lat": "50.65020", + "lon": "4.26370", + "id": 156632951, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Montoisy", + "lat": "50.64780", + "lon": "4.25881", + "id": 36262 + } + ], + "matchStreet": 1 + }, + { + "name": "Arlon", + "names": { + "de": "Arel", + "ru": "Арлон", + "la": "Orolaunum", + "lb": "Arel", + "wa": "Årlon", + "fr": "Arlon", + "nl": "Aarlen" + }, + "lat": "49.68276", + "lon": "5.81192", + "id": 63111399, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Tandel (Quatre Vents)", + "lat": "49.69696", + "lon": "5.81016", + "id": 227645 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Stavelot", + "names": { + "de": "Stablo", + "ru": "Ставло", + "wa": "Ståvleu", + "fr": "Stavelot" + }, + "lat": "50.39409", + "lon": "5.93084", + "id": 79445596, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Place Emile Jamar", + "lat": "50.45480", + "lon": "5.95144", + "id": 222863, + "buildings": [] + }, + { + "name": "Rue Emile Goedert", + "lat": "50.45446", + "lon": "5.95199", + "id": 43727 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Mons", + "names": { + "ar": "مونس", + "de": "Bergen", + "ru": "Монс", + "la": "Montes", + "fr": "Mons", + "nl": "Bergen", + "sr": "Монс" + }, + "lat": "50.45496", + "lon": "3.95197", + "id": 893278148, + "type": "CITY", + "listOfStreets": [ + { + "name": "Rue Emile Limauge", + "lat": "50.47297", + "lon": "3.92139", + "id": 164192 + }, + { + "name": "Rue Emile Blairon", + "lat": "50.40774", + "lon": "4.01100", + "id": 208242 + }, + { + "name": "Rue Émile Vandervelde (Bertaimont)", + "lat": "50.44018", + "lon": "3.94135", + "id": 26027 + }, + { + "name": "Rue Émile Wauquier (Nouvelles)", + "lat": "50.40366", + "lon": "3.96287", + "id": 245331 + }, + { + "name": "Rue Émile Jambe", + "lat": "50.46453", + "lon": "4.04595", + "id": 87736 + }, + { + "name": "Avenue du Gouverneur Émile Cornez", + "lat": "50.44856", + "lon": "3.96207", + "id": 202622 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Thulin", + "names": { + "ru": "Тюлен" + }, + "lat": "50.42929", + "lon": "3.73932", + "id": 1376491703, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.42997", + "lon": "3.74218", + "id": 176292 + } + ], + "matchStreet": 1 + }, + { + "name": "Forest - Vorst", + "names": { + "ru": "Форе-Ворст", + "fr": "Forest", + "nl": "Vorst" + }, + "lat": "50.80914", + "lon": "4.31776", + "id": 66197751, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Square Emile des Grées du Loû - Emile des Grées du Loû Square", + "names": { + "fr": "Square Emile des Grées du Loû", + "nl": "Emile des Grées du Loû Square" + }, + "lat": "50.80530", + "lon": "4.31123", + "id": 7267 + }, + { + "name": "Rue Emile Pathé - Emile Pathéstraat", + "names": { + "fr": "Rue Emile Pathé", + "nl": "Emile Pathéstraat" + }, + "lat": "50.79933", + "lon": "4.30452", + "id": 135409 + }, + { + "name": "Rue Émile Feron - Émile Feronstraat", + "lat": "50.82974", + "lon": "4.33477", + "id": 222734 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Hoeilaart", + "names": { + "ru": "Хуларт", + "fr": "Hoeilaert" + }, + "lat": "50.76748", + "lon": "4.47440", + "id": 252608972, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Vandenbroeckstraat", + "lat": "50.76750", + "lon": "4.47626", + "id": 76849 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Angre", + "names": { + "ru": "Ангр" + }, + "lat": "50.36698", + "lon": "3.69544", + "id": 248076301, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Cornez", + "lat": "50.36827", + "lon": "3.69413", + "id": 172066 + } + ], + "matchStreet": 1 + }, + { + "name": "Habay-la-Neuve", + "names": { + "de": "Neu-Habich", + "lb": "Neihabech", + "wa": "Hâbâ-la-Niëf", + "fr": "Habay-la-Neuve" + }, + "lat": "49.72861", + "lon": "5.65038", + "id": 292129645, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Baudrux", + "names": { + "etymology:wikidata": "Q3588328" + }, + "lat": "49.72792", + "lon": "5.64650", + "id": 12787 + } + ], + "matchStreet": 1 + }, + { + "name": "Quévy", + "lat": "50.36672", + "lon": "3.94390", + "id": 662071665, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Wauquier (Asquillies)", + "lat": "50.40050", + "lon": "3.96312", + "id": 136588 + } + ], + "matchStreet": 1 + }, + { + "name": "Chièvres", + "lat": "50.58798", + "lon": "3.80569", + "id": 64619788, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Dooms", + "lat": "50.58670", + "lon": "3.80520", + "id": 198153 + } + ], + "matchStreet": 1 + }, + { + "name": "Court-Saint-Etienne", + "names": { + "ru": "Кур-Сен-Этьен", + "wa": "Coû-Sint-Stiene" + }, + "lat": "50.64432", + "lon": "4.56857", + "id": 292551547, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Henricot", + "lat": "50.64566", + "lon": "4.56684", + "id": 9739 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Hastière-Lavaux", + "names": { + "ru": "Астьер-Лаво", + "wa": "Li Vå-dlé-Astire" + }, + "lat": "50.21654", + "lon": "4.82424", + "id": 664657228, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Binet", + "lat": "50.21627", + "lon": "4.82517", + "id": 127767, + "buildings": [], + "intersectedStreets": [ + { + "name": "Rue des Vignes", + "lat": "50.21627", + "lon": "4.82517" + }, + { + "name": "Rue Marcel Lespagne", + "lat": "50.21640", + "lon": "4.82476" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Gaillemarde", + "lat": "50.72232", + "lon": "4.44423", + "id": 2508733269, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Rue Emile Semal", + "lat": "50.72241", + "lon": "4.44262", + "id": 61341 + } + ], + "matchStreet": 1 + }, + { + "name": "Braibant", + "names": { + "wa": "Braibant", + "nl": "Braibant" + }, + "lat": "50.31291", + "lon": "5.06296", + "id": 366511807, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Impasse Émile Romnée", + "lat": "50.31254", + "lon": "5.06431", + "id": 152601, + "intersectedStreets": [ + { + "name": "Rue d'Yvoir", + "lat": "50.31248", + "lon": "5.06435" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Lessines", + "names": { + "ru": "Лесин", + "fr": "Lessines", + "nl": "Lessen" + }, + "lat": "50.71230", + "lon": "3.83011", + "id": 31353523, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Ollignies)", + "lat": "50.68658", + "lon": "3.86146", + "id": 37258 + }, + { + "name": "Boulevard Emile Schevenels", + "lat": "50.71260", + "lon": "3.82474", + "id": 28492 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Zemst", + "names": { + "ru": "Земст" + }, + "lat": "50.98421", + "lon": "4.46508", + "id": 270762769, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Verbruggestraat", + "lat": "50.97490", + "lon": "4.47656", + "id": 49035 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Dampremy", + "lat": "50.41862", + "lon": "4.43225", + "id": 673999641, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Fourcault", + "names": { + "old_name": "Rue Frison" + }, + "lat": "50.42237", + "lon": "4.43419", + "id": 208181 + } + ], + "matchStreet": 1 + }, + { + "name": "Vilvoorde", + "names": { + "ru": "Вилворде", + "fr": "Vilvorde", + "nl": "Vilvoorde" + }, + "lat": "50.93089", + "lon": "4.43195", + "id": 1677252814, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Verhaerenstraat", + "lat": "50.93782", + "lon": "4.43243", + "id": 51153 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Sint-Pieters-Leeuw", + "names": { + "ru": "Синт-Питерс-Леув", + "fr": "Leeuw-Saint-Pierre", + "nl": "Sint-Pieters-Leeuw" + }, + "lat": "50.78003", + "lon": "4.24437", + "id": 251471298, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Vandersteenenstraat", + "lat": "50.78189", + "lon": "4.27173", + "id": 46231 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Erquelinnes", + "names": { + "ru": "Эркелин" + }, + "lat": "50.31022", + "lon": "4.12414", + "id": 477193038, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Bosseaux", + "lat": "50.30627", + "lon": "4.15260", + "id": 214086 + }, + { + "name": "Rue Émile Vandervelde (Solre-sur-Sambre)", + "lat": "50.30862", + "lon": "4.15785", + "id": 212044 + }, + { + "name": "Rue Émile-Zola", + "lat": "50.29086", + "lon": "4.11382", + "id": 661 + } + ], + "matchStreet": 1 + }, + { + "name": "Schepdaal", + "names": { + "ru": "Схепдал", + "fr": "Schepdael" + }, + "lat": "50.83486", + "lon": "4.19197", + "id": 251471297, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Eylenboschstraat", + "lat": "50.83394", + "lon": "4.19261", + "id": 166357 + } + ], + "matchStreet": 1 + }, + { + "name": "Grez-Doiceau", + "names": { + "ru": "Грес-Дуасо", + "wa": "Gré", + "fr": "Grez-Doiceau", + "nl": "Graven" + }, + "lat": "50.73862", + "lon": "4.69623", + "id": 292561459, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Nethen)", + "lat": "50.78206", + "lon": "4.69022", + "id": 137435 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Piéton", + "lat": "50.44096", + "lon": "4.29816", + "id": 449287621, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Duhoux", + "lat": "50.44041", + "lon": "4.30098", + "id": 143586 + } + ], + "matchStreet": 1 + }, + { + "name": "Solre-sur-Sambre", + "names": { + "ru": "Сольр-сюр-Самбр" + }, + "lat": "50.30677", + "lon": "4.15584", + "id": 669804720, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Bosseaux", + "lat": "50.30625", + "lon": "4.15262", + "id": 214087 + }, + { + "name": "Rue Émile Vandervelde", + "lat": "50.30861", + "lon": "4.15785", + "id": 212045 + } + ], + "matchStreet": 1 + }, + { + "name": "Menen", + "names": { + "ru": "Менен", + "fr": "Menin", + "vls": "Mjinde" + }, + "lat": "50.79699", + "lon": "3.11565", + "id": 1678309134, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Em. Zolastraat", + "lat": "50.80668", + "lon": "3.10827", + "id": 237572 + }, + { + "name": "Em. Vanderveldestraat", + "lat": "50.80741", + "lon": "3.11544", + "id": 237394 + }, + { + "name": "Em. Vandeveldestraat", + "lat": "50.80726", + "lon": "3.11570", + "id": 178656 + }, + { + "name": "Emile Zolastraat", + "lat": "50.80649", + "lon": "3.10802", + "id": 149312 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "La Cantine", + "lat": "50.68406", + "lon": "4.29746", + "id": 5443456831, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Rue Emile Schampaert", + "lat": "50.68466", + "lon": "4.29962", + "id": 43681 + } + ], + "matchStreet": 1 + }, + { + "name": "Sirault", + "names": { + "ru": "Сиро" + }, + "lat": "50.50606", + "lon": "3.78812", + "id": 189249614, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Lété", + "lat": "50.50407", + "lon": "3.78936", + "id": 9232 + }, + { + "name": "Rue Emile Vandervelde", + "lat": "50.50506", + "lon": "3.78528", + "id": 9012 + } + ], + "matchStreet": 1 + }, + { + "name": "Ramillies", + "names": { + "ru": "Рамийи" + }, + "lat": "50.63669", + "lon": "4.91490", + "id": 292561468, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Laurent", + "lat": "50.66652", + "lon": "4.87368", + "id": 232186, + "intersectedStreets": [ + { + "name": "Rue de Laloux", + "lat": "50.66685", + "lon": "4.87426" + }, + { + "name": "Rue de L'Abyme", + "lat": "50.66645", + "lon": "4.87342" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Orp-Jauche", + "names": { + "ru": "Орп-Жош", + "wa": "Oû-Djåce", + "fr": "Orp-Jauche" + }, + "lat": "50.70010", + "lon": "4.98899", + "id": 292561469, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Landeut", + "lat": "50.72373", + "lon": "4.96571", + "id": 150104 + }, + { + "name": "Avenue Emile Vandervelde (Maret)", + "lat": "50.71392", + "lon": "4.99309", + "id": 135836, + "intersectedStreets": [ + { + "name": "Place de Maret", + "lat": "50.71422", + "lon": "4.99307" + }, + { + "name": "Rue Eugène Malevé", + "lat": "50.71422", + "lon": "4.99307" + }, + { + "name": "Rue Léon Jacquemin", + "lat": "50.70751", + "lon": "4.99264" + }, + { + "name": "Rue du Bonsopre", + "lat": "50.71243", + "lon": "4.99324" + }, + { + "name": "Place du 11e Dragons Français", + "lat": "50.70381", + "lon": "4.99071" + }, + { + "name": "Rue Sainte-Barbe (Orp-le-Grand)", + "lat": "50.70560", + "lon": "4.99153" + } + ] + }, + { + "name": "Rue Emile Looze (Maret)", + "lat": "50.71415", + "lon": "4.99912", + "id": 135833 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Gent", + "enName": "Ghent", + "names": { + "de": "Gent", + "hi": "खेंट", + "be": "Гент", + "ru": "Гент", + "ast": "Gante", + "pt": "Gante", + "bg": "Гент", + "lt": "Gentas", + "lv": "Ģente", + "fr": "Gand", + "ug": "Gant", + "fy": "Gint", + "oc": "Gant", + "ka": "გენტი", + "uk": "Гент", + "ca": "Gant", + "sr": "Гент", + "mn": "Гент", + "ko": "헨트", + "os": "Гент", + "gl": "Gante - Gent", + "mr": "गेंट", + "el": "Γάνδη", + "eo": "Gento", + "it": "Gand", + "my": "ဂင့်မြို့", + "es": "Gante", + "zh": "根特", + "eu": "Gante", + "ar": "غنت", + "na": "Ghent", + "th": "เกนต์", + "la": "Gandavum", + "ja": "ヘント", + "tl": "Gante", + "vo": "Ghent", + "fa": "گنت", + "pl": "Gandawa", + "he": "גנט", + "nl": "Gent" + }, + "lat": "51.05383", + "lon": "3.72501", + "id": 1668655163, + "type": "CITY", + "listOfStreets": [ + { + "name": "Emile Braunplein", + "names": { + "alt_name": "Braunplein" + }, + "lat": "51.05389", + "lon": "3.72445", + "id": 13411 + }, + { + "name": "Emile Clauslaan", + "lat": "51.03656", + "lon": "3.72211", + "id": 174215 + }, + { + "name": "Emile Moysonlaan", + "lat": "51.04872", + "lon": "3.74722", + "id": 193683 + }, + { + "name": "Emile Andelhofstraat", + "lat": "51.02353", + "lon": "3.67094", + "id": 107333 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Louvain-la-Neuve", + "names": { + "de": "Neu-Löwen", + "ru": "Лувен-ла-Нёв" + }, + "lat": "50.66820", + "lon": "4.61288", + "id": 60172442, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Francqui", + "lat": "50.65896", + "lon": "4.62312", + "id": 14519 + }, + { + "name": "Rue Émile Goes (Biéreau)", + "lat": "50.66461", + "lon": "4.61765", + "id": 94546 + }, + { + "name": "Avenue Émile Verhaeren (Bruyères)", + "lat": "50.66147", + "lon": "4.60887", + "id": 94576, + "intersectedStreets": [ + { + "name": "Avenue des Arts (Bruyères)", + "lat": "50.66256", + "lon": "4.60748" + }, + { + "name": "Rue Victor Horta (Bruyères)", + "lat": "50.66256", + "lon": "4.60748" + }, + { + "name": "Rue Achille Chavée (Bruyères)", + "lat": "50.66187", + "lon": "4.60838" + }, + { + "name": "Rue Marie Gevers (Bruyères)", + "lat": "50.66187", + "lon": "4.60838" + }, + { + "name": "Rue Albert Mockel (Bruyères)", + "lat": "50.66140", + "lon": "4.60898" + }, + { + "name": "Parvis de la Cantilène (Bruyères)", + "lat": "50.66160", + "lon": "4.60999" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Thiméon", + "lat": "50.49035", + "lon": "4.42813", + "id": 673982201, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Vandervelde", + "names": { + "alt_name": "Rue Emile Vandervelde" + }, + "lat": "50.48106", + "lon": "4.43007", + "id": 138442 + } + ], + "matchStreet": 1 + }, + { + "name": "Saint-Ghislain", + "names": { + "ru": "Сен-Гилен", + "bg": "Сен Гислен", + "fa": "سن-گیزلن" + }, + "lat": "50.44775", + "lon": "3.81953", + "id": 79435626, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Lenoir", + "lat": "50.50740", + "lon": "3.77146", + "id": 9540 + }, + { + "name": "Rue Emile Lété", + "lat": "50.51858", + "lon": "3.82215", + "id": 177700 + }, + { + "name": "Rue Emile Vandervelde (Sirault)", + "lat": "50.50506", + "lon": "3.78531", + "id": 9011 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Sombreffe", + "names": { + "ru": "Сомбреф", + "wa": "Sombrefe", + "nl": "Sombreffe" + }, + "lat": "50.52466", + "lon": "4.60258", + "id": 187197428, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Pirson", + "lat": "50.53964", + "lon": "4.61565", + "id": 39355 + }, + { + "name": "Rue Émile Vandervelde (Ligny)", + "lat": "50.50950", + "lon": "4.56746", + "id": 24483 + } + ], + "matchStreet": 1 + }, + { + "name": "Monceau-sur-Sambre", + "lat": "50.41702", + "lon": "4.38241", + "id": 450191735, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Cité Emile Demoulin", + "lat": "50.42151", + "lon": "4.38084", + "id": 206924 + }, + { + "name": "Rue Émile Constant", + "lat": "50.41615", + "lon": "4.38816", + "id": 160847 + }, + { + "name": "Rue Emile Vandervelde", + "lat": "50.41590", + "lon": "4.37561", + "id": 212071 + }, + { + "name": "Rue Emile Leclercq", + "lat": "50.41668", + "lon": "4.37161", + "id": 219791 + } + ], + "matchStreet": 1 + }, + { + "name": "Vielsalm", + "names": { + "ru": "Вьельсальм" + }, + "lat": "50.28837", + "lon": "5.91702", + "id": 251707435, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Tromme", + "lat": "50.32363", + "lon": "5.90940", + "id": 20007 + } + ], + "matchStreet": 1 + }, + { + "name": "Marchienne-au-Pont", + "lat": "50.40671", + "lon": "4.39601", + "id": 450191739, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Brunet", + "lat": "50.39479", + "lon": "4.39303", + "id": 207018 + }, + { + "name": "Rue des Bateliers", + "names": { + "old_name": "Rue Émile Vandervelde" + }, + "lat": "50.40809", + "lon": "4.39500", + "id": 207281 + }, + { + "name": "Rue Saint Emile", + "lat": "50.42439", + "lon": "4.40146", + "id": 160353 + }, + { + "name": "Rue Émile Royer", + "lat": "50.42276", + "lon": "4.41992", + "id": 142975 + }, + { + "name": "Rue Émile Gantois", + "lat": "50.42129", + "lon": "4.41455", + "id": 142985 + } + ], + "matchStreet": 1 + }, + { + "name": "Landen", + "names": { + "ru": "Ланден" + }, + "lat": "50.75301", + "lon": "5.08131", + "id": 77802451, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Moyaertslaan", + "lat": "50.74737", + "lon": "5.08620", + "id": 203470, + "intersectedStreets": [ + { + "name": "Ernest Pitonlaan", + "lat": "50.74710", + "lon": "5.08491" + }, + { + "name": "Filips de Goedelaan", + "lat": "50.74737", + "lon": "5.08620" + }, + { + "name": "Philips de Goedelaan", + "lat": "50.74737", + "lon": "5.08620" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Berchem-Sainte-Agathe - Sint-Agatha-Berchem", + "names": { + "ru": "Беркем-Сент-Агат", + "fr": "Berchem-Sainte-Agathe", + "li": "Sint-Agatha-Berchem", + "nl": "Sint-Agatha-Berchem" + }, + "lat": "50.86398", + "lon": "4.29269", + "id": 66194565, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Van Overstraeten - Emile Van Overstraetenstraat", + "names": { + "fr": "Rue Emile Van Overstraeten", + "nl": "Emile Van Overstraetenstraat" + }, + "lat": "50.86218", + "lon": "4.29003", + "id": 4997 + }, + { + "name": "Rue Emile Heylens - Emile Heylensstraat", + "names": { + "fr": "Rue Emile Heylens", + "nl": "Emile Heylensstraat" + }, + "lat": "50.85681", + "lon": "4.29291", + "id": 5001 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Binche", + "names": { + "ru": "Бенш" + }, + "lat": "50.41030", + "lon": "4.16517", + "id": 428181182, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Zola (Péronnes-lez-Binche)", + "lat": "50.43840", + "lon": "4.15277", + "id": 186773 + }, + { + "name": "Rue Emile Vinck (Péronnes-lez-Binche)", + "lat": "50.43954", + "lon": "4.15137", + "id": 159548 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Bois-de-Villers", + "names": { + "wa": "Li Bwès-d'-Vilé" + }, + "lat": "50.38997", + "lon": "4.82338", + "id": 686149534, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Mazy", + "lat": "50.39042", + "lon": "4.80824", + "id": 185944 + }, + { + "name": "Rue Emile Mahaux", + "lat": "50.37429", + "lon": "4.83752", + "id": 186072 + } + ], + "matchStreet": 1 + }, + { + "name": "Boom", + "names": { + "ru": "Бом", + "lt": "Bomas" + }, + "lat": "51.08738", + "lon": "4.36672", + "id": 173252843, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Van Reethstraat", + "lat": "51.10022", + "lon": "4.36734", + "id": 67959 + }, + { + "name": "Emile Vanderveldestraat", + "lat": "51.08684", + "lon": "4.36149", + "id": 25994 + }, + { + "name": "Emile Verhaerenstraat", + "lat": "51.09917", + "lon": "4.37475", + "id": 418 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "La Hulpe", + "names": { + "ru": "Ла-Юльп", + "fr": "La Hulpe", + "nl": "Terhulpen" + }, + "lat": "50.73150", + "lon": "4.47965", + "id": 138160966, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Semal", + "lat": "50.72241", + "lon": "4.44262", + "id": 61340 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Leugnies", + "lat": "50.22464", + "lon": "4.19616", + "id": 668473443, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Damien", + "lat": "50.22574", + "lon": "4.19371", + "id": 236891 + } + ], + "matchStreet": 1 + }, + { + "name": "Ollignies", + "names": { + "ru": "Оллиньи", + "fr": "Ollignies", + "nl": "Woelingen" + }, + "lat": "50.68742", + "lon": "3.85987", + "id": 33540734, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.68656", + "lon": "3.86144", + "id": 37259 + } + ], + "matchStreet": 1 + }, + { + "name": "Hannut", + "names": { + "ru": "Анню", + "wa": "Haneù", + "fr": "Hannut", + "nl": "Hannuit" + }, + "lat": "50.67246", + "lon": "5.07800", + "id": 64817311, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Duchesne", + "lat": "50.69487", + "lon": "5.03067", + "id": 22847 + }, + { + "name": "Rue Emile Roder", + "lat": "50.65424", + "lon": "5.12933", + "id": 141974 + }, + { + "name": "Rue Emile Martin", + "lat": "50.66500", + "lon": "5.07957", + "id": 138950 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Soignies", + "names": { + "de": "Söglingen", + "ru": "Суаньи", + "fr": "Soignies", + "nl": "Zinnik" + }, + "lat": "50.57753", + "lon": "4.07329", + "id": 79443904, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Carrières)", + "lat": "50.57085", + "lon": "4.07908", + "id": 90219 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Awans", + "lat": "50.66697", + "lon": "5.46201", + "id": 260096196, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Paul Emile Janson", + "lat": "50.66370", + "lon": "5.47374", + "id": 32565 + } + ], + "matchStreet": 1 + }, + { + "name": "Grâce-Hollogne", + "names": { + "ru": "Грас-Олонь", + "wa": "Gråce-Hologne", + "fr": "Grâce-Hollogne" + }, + "lat": "50.64496", + "lon": "5.50314", + "id": 746136317, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Verhaeren", + "lat": "50.64134", + "lon": "5.50685", + "id": 137402 + }, + { + "name": "Avenue Emile Vandervelde", + "lat": "50.64817", + "lon": "5.50625", + "id": 82303, + "buildings": [], + "intersectedStreets": [ + { + "name": "Avenue Joseph Wauters", + "lat": "50.64641", + "lon": "5.50630" + }, + { + "name": "Rue Mathieu de Lexhy", + "lat": "50.64817", + "lon": "5.50625" + } + ] + }, + { + "name": "Rue Emile Wiket", + "lat": "50.63685", + "lon": "5.50756", + "id": 33491 + }, + { + "name": "Rue Émile Zola (Grâce-Berleur)", + "lat": "50.63152", + "lon": "5.50366", + "id": 85835 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Auderghem - Oudergem", + "names": { + "ru": "Одергем", + "fr": "Auderghem", + "nl": "Oudergem" + }, + "lat": "50.81566", + "lon": "4.43313", + "id": 66509952, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Steeno - Emile Steenostraat", + "names": { + "fr": "Rue Emile Steeno", + "nl": "Emile Steenostraat" + }, + "lat": "50.81825", + "lon": "4.42961", + "id": 85219 + }, + { + "name": "Rue Emile Idiers - Emile Idiersstraat", + "names": { + "fr": "Rue Émile Idiers", + "nl": "Émile Idiersstraat" + }, + "lat": "50.81685", + "lon": "4.42775", + "id": 14698 + }, + { + "name": "Avenue Emile Laine - Emile Lainelaan", + "names": { + "fr": "Avenue Emile Laine", + "nl": "Emile Lainelaan" + }, + "lat": "50.82567", + "lon": "4.42886", + "id": 242481 + }, + { + "name": "Avenue Gabriel Emile Lebon - Gabriel Emile Lebonlaan", + "names": { + "fr": "Avenue Gabriel Emile Lebon", + "nl": "Gabriel Emile Lebonlaan" + }, + "lat": "50.82135", + "lon": "4.40947", + "id": 2942 + }, + { + "name": "Rue Jules Emile Raymond - Jules Emile Raymondstraat", + "names": { + "fr": "Rue Jules Emile Raymond", + "nl": "Jules Emile Raymondstraat" + }, + "lat": "50.82436", + "lon": "4.40706", + "id": 2943 + }, + { + "name": "Rue Emile Rotiers - Emile Rotiersstraat", + "names": { + "fr": "Rue Emile Rotiers", + "nl": "Emile Rotiersstraat" + }, + "lat": "50.80706", + "lon": "4.43371", + "id": 35715 + }, + { + "name": "Rue Paul Emile Lessire - Paul Emile Lessirestraat", + "names": { + "fr": "Rue Paul Emile Lessire", + "nl": "Paul Emile Lessirestraat" + }, + "lat": "50.80486", + "lon": "4.43676", + "id": 35723 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Grâce-Berleur", + "lat": "50.63233", + "lon": "5.50119", + "id": 746136310, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Zola", + "lat": "50.63152", + "lon": "5.50368", + "id": 85836 + } + ], + "matchStreet": 1 + }, + { + "name": "Fontaine-Valmont", + "lat": "50.32047", + "lon": "4.21396", + "id": 673921851, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.32034", + "lon": "4.21564", + "id": 224569 + } + ], + "matchStreet": 1 + }, + { + "name": "Havré", + "names": { + "ru": "Авре", + "fr": "Havré" + }, + "lat": "50.46435", + "lon": "4.04539", + "id": 480286655, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Jambe", + "lat": "50.46451", + "lon": "4.04593", + "id": 87737 + } + ], + "matchStreet": 1 + }, + { + "name": "Courcelles", + "names": { + "ru": "Курсель" + }, + "lat": "50.46015", + "lon": "4.37634", + "id": 331198493, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Bronchain", + "lat": "50.46448", + "lon": "4.37855", + "id": 146474 + }, + { + "name": "Rue Emile Thilmans", + "lat": "50.44884", + "lon": "4.34926", + "id": 153834 + }, + { + "name": "Rue Émile Duployé", + "lat": "50.46152", + "lon": "4.38764", + "id": 209170 + }, + { + "name": "Rue Émile Vandervelde (Souvret)", + "lat": "50.44548", + "lon": "4.35496", + "id": 209247 + }, + { + "name": "Rue Émile Turlot", + "lat": "50.45805", + "lon": "4.38674", + "id": 131615 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Villers-la-Ville", + "names": { + "ru": "Виллер-ла-Виль" + }, + "lat": "50.57903", + "lon": "4.53242", + "id": 130610211, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Leger", + "lat": "50.57619", + "lon": "4.52744", + "id": 58261 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Charleroi", + "names": { + "ar": "شارلوروا", + "de": "Charleroi", + "ru": "Шарлеруа", + "la": "Caroloregium", + "lt": "Šarlerua", + "wa": "Tchålerwè", + "fr": "Charleroi", + "hu": "Charleroi", + "nl": "Charleroi", + "sr": "Шарлроа" + }, + "lat": "50.41203", + "lon": "4.44363", + "id": 9002746, + "type": "CITY", + "listOfStreets": [ + { + "name": "Boulevard Emile Devreux", + "lat": "50.40749", + "lon": "4.45041", + "id": 240623 + }, + { + "name": "Rue Jules Ruhl", + "names": { + "old_name": "Rue Emile Vandervelde" + }, + "lat": "50.40380", + "lon": "4.40932", + "id": 129009 + }, + { + "name": "Avenue Emile Rousseaux", + "lat": "50.40260", + "lon": "4.42676", + "id": 129025 + }, + { + "name": "Cité Emile Demoulin", + "lat": "50.42149", + "lon": "4.38084", + "id": 206923 + }, + { + "name": "Rue Émile Constant", + "lat": "50.41613", + "lon": "4.38814", + "id": 160846 + }, + { + "name": "Rue Emile Vandervelde (Monceau-sur-Sambre)", + "lat": "50.41589", + "lon": "4.37561", + "id": 212070 + }, + { + "name": "Rue Émile Vandervelde (Lodelinsart)", + "lat": "50.42318", + "lon": "4.45680", + "id": 121979 + }, + { + "name": "Square Émile Buisset", + "lat": "50.38316", + "lon": "4.45341", + "id": 130206 + }, + { + "name": "Rue Emile Brunet", + "lat": "50.39479", + "lon": "4.39303", + "id": 207017 + }, + { + "name": "Rue Émile Nonnon", + "lat": "50.40142", + "lon": "4.44266", + "id": 127156 + }, + { + "name": "Rue Émile Vandervelde (Marcinelle)", + "lat": "50.39180", + "lon": "4.44043", + "id": 140477 + }, + { + "name": "Rue Émile Cliquet", + "lat": "50.39904", + "lon": "4.44820", + "id": 127181 + }, + { + "name": "Place Émile Bertaux", + "lat": "50.46685", + "lon": "4.43161", + "id": 208148 + }, + { + "name": "Rue Emile Fourcault", + "names": { + "old_name": "Rue Frison" + }, + "lat": "50.42237", + "lon": "4.43419", + "id": 208180 + }, + { + "name": "Rue Émile Servais", + "lat": "50.36865", + "lon": "4.40698", + "id": 184629 + }, + { + "name": "Rue des Bateliers (Marchienne-au-Pont)", + "names": { + "old_name": "Rue Émile Vandervelde (Marchienne-au-Pont)" + }, + "lat": "50.40809", + "lon": "4.39500", + "id": 207280 + }, + { + "name": "Rue Émile Vandervelde (Roux)", + "lat": "50.43980", + "lon": "4.37539", + "id": 149937 + }, + { + "name": "Rue Emile Ruiters", + "lat": "50.45205", + "lon": "4.41494", + "id": 160225 + }, + { + "name": "Rue Émile Tumelaire", + "lat": "50.41058", + "lon": "4.44852", + "id": 111096 + }, + { + "name": "Rue Émile Dutrieux", + "lat": "50.40027", + "lon": "4.48126", + "id": 123421 + }, + { + "name": "Place Emile Buisset", + "lat": "50.40651", + "lon": "4.43918", + "id": 223781 + }, + { + "name": "Rue Émile Strimelle", + "lat": "50.43507", + "lon": "4.42573", + "id": 160334 + }, + { + "name": "Rue Saint Emile (Marchienne-au-Pont)", + "lat": "50.42439", + "lon": "4.40146", + "id": 160352 + }, + { + "name": "Place Emile Moureau", + "lat": "50.41287", + "lon": "4.35713", + "id": 204393 + }, + { + "name": "Rue Émile Royer", + "lat": "50.42276", + "lon": "4.41992", + "id": 142974 + }, + { + "name": "Rue Émile Gantois", + "lat": "50.42129", + "lon": "4.41455", + "id": 142984 + }, + { + "name": "Rue Emile Leclercq", + "lat": "50.41667", + "lon": "4.37159", + "id": 219790 + }, + { + "name": "Place Emile Vandevelde", + "lat": "50.41526", + "lon": "4.46159", + "id": 122633 + }, + { + "name": "Rue Émile Thibaut", + "lat": "50.39336", + "lon": "4.40567", + "id": 140119 + }, + { + "name": "Rue Émile Vandervelde (Couillet)", + "lat": "50.39195", + "lon": "4.46837", + "id": 165790 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Morlanwelz", + "names": { + "ru": "Морланве" + }, + "lat": "50.45099", + "lon": "4.25235", + "id": 470539132, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.44815", + "lon": "4.25810", + "id": 143911 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Longdoz", + "lat": "50.63006", + "lon": "5.58065", + "id": 5483428, + "type": "SUBURB", + "listOfStreets": [ + { + "name": "Boulevard Emile de Laveleye", + "lat": "50.62324", + "lon": "5.58245", + "id": 32336, + "buildings": [], + "intersectedStreets": [ + { + "name": "Quai Mativa", + "lat": "50.62296", + "lon": "5.58110" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Mont-sur-Marchienne", + "lat": "50.39020", + "lon": "4.40464", + "id": 674001818, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Jules Ruhl", + "names": { + "old_name": "Rue Emile Vandervelde" + }, + "lat": "50.40380", + "lon": "4.40932", + "id": 129010 + }, + { + "name": "Rue Émile Servais", + "lat": "50.36865", + "lon": "4.40698", + "id": 184630 + }, + { + "name": "Rue Émile Thibaut", + "lat": "50.39336", + "lon": "4.40567", + "id": 140120 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Anderlues", + "names": { + "ru": "Андерлю" + }, + "lat": "50.40800", + "lon": "4.26960", + "id": 470539134, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.40499", + "lon": "4.27040", + "id": 142366 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Puhain", + "lat": "50.66048", + "lon": "4.14069", + "id": 3052047966, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Avenue Emile Geerts", + "lat": "50.66049", + "lon": "4.14343", + "id": 69948 + } + ], + "matchStreet": 1 + }, + { + "name": "Fleurus", + "names": { + "ru": "Флёрюс" + }, + "lat": "50.48235", + "lon": "4.55051", + "id": 64802854, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Hautem", + "lat": "50.46282", + "lon": "4.55439", + "id": 128218 + }, + { + "name": "Rue Emile Vandervelde", + "lat": "50.48091", + "lon": "4.54828", + "id": 86932 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Tournai", + "names": { + "ru": "Турне", + "nl": "Doornik" + }, + "lat": "50.60566", + "lon": "3.38782", + "id": 36162975, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Place Paul-Emile Janson", + "lat": "50.60707", + "lon": "3.38933", + "id": 115117 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Beaumont", + "names": { + "ru": "Бомон" + }, + "lat": "50.23553", + "lon": "4.23832", + "id": 64608294, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Culot (Leval-Chaudeville)", + "lat": "50.23679", + "lon": "4.19663", + "id": 164130 + }, + { + "name": "Rue Emile Damien", + "lat": "50.22589", + "lon": "4.19246", + "id": 109463 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Pont-à-Celles", + "names": { + "ru": "Понт-а-Сель" + }, + "lat": "50.51225", + "lon": "4.36174", + "id": 325851346, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Vandervelde (Thiméon)", + "names": { + "alt_name": "Rue Emile Vandervelde (Thiméon)" + }, + "lat": "50.48105", + "lon": "4.43007", + "id": 138441 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Andenne", + "names": { + "ru": "Анден", + "wa": "Andene", + "fr": "Andenne", + "nl": "Andenne" + }, + "lat": "50.49009", + "lon": "5.10439", + "id": 29827642, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Namêche)", + "lat": "50.47495", + "lon": "4.99771", + "id": 214053 + }, + { + "name": "Rue Emile Godfrind", + "lat": "50.49564", + "lon": "5.08858", + "id": 60504 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Gembloux", + "names": { + "ru": "Жамблу", + "wa": "Djiblou", + "fr": "Gembloux", + "nl": "Gembloers" + }, + "lat": "50.56055", + "lon": "4.69215", + "id": 60302311, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Émile Labarre", + "lat": "50.59404", + "lon": "4.66848", + "id": 62536 + }, + { + "name": "Rue Émile Somville", + "lat": "50.55672", + "lon": "4.67608", + "id": 66934 + }, + { + "name": "Rue Émile Pirson", + "lat": "50.50821", + "lon": "4.67378", + "id": 32207 + }, + { + "name": "Rue Emile Dewez (Beuzet)", + "lat": "50.54048", + "lon": "4.73045", + "id": 62988 + }, + { + "name": "Rue Emile Marchal", + "lat": "50.50341", + "lon": "4.71610", + "id": 221782 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Nieuwpoort-Bad", + "names": { + "ru": "Ньивпорт-Бад", + "fr": "Nieuport-Bains" + }, + "lat": "51.14781", + "lon": "2.71566", + "id": 61337572, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Verhaerenlaan", + "lat": "51.14655", + "lon": "2.71811", + "id": 216019 + } + ], + "matchStreet": 1 + }, + { + "name": "Oostduinkerke", + "names": { + "de": "Ostdünkirchen", + "ru": "Остдёйнкерке", + "fr": "Ostdunkerque" + }, + "lat": "51.11563", + "lon": "2.68126", + "id": 720863546, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Verhaerenlaan", + "names": { + "alt_name": "Verhaerenlaan" + }, + "lat": "51.12650", + "lon": "2.69611", + "id": 132770 + } + ], + "matchStreet": 1 + }, + { + "name": "Sint-Michiels", + "names": { + "fr": "Saint-Michel", + "vls": "Sinte-Machiels" + }, + "lat": "51.18846", + "lon": "3.21116", + "id": 1646090638, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Emile Bethuynelaan", + "lat": "51.16468", + "lon": "3.18908", + "id": 205074 + } + ], + "matchStreet": 1 + }, + { + "name": "Knokke-Heist", + "names": { + "ru": "Кнокке-Хейст", + "fr": "Knocke-Heist", + "vls": "Knokke-Heist" + }, + "lat": "51.33961", + "lon": "3.27021", + "id": 79387375, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Emile Verhaerenlaan (Knokke)", + "lat": "51.35294", + "lon": "3.30388", + "id": 162981 + }, + { + "name": "Emile Raespad", + "lat": "51.33845", + "lon": "3.28395", + "id": 222496 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Floreffe", + "names": { + "ru": "Флореф", + "wa": "Florefe", + "nl": "Floreffe" + }, + "lat": "50.43481", + "lon": "4.75888", + "id": 414028537, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Lessire", + "lat": "50.42733", + "lon": "4.75584", + "id": 221823 + }, + { + "name": "Rue Émile Lorent", + "lat": "50.44791", + "lon": "4.74305", + "id": 93851 + } + ], + "matchStreet": 1 + }, + { + "name": "Limelette", + "names": { + "wa": "Limlete" + }, + "lat": "50.68050", + "lon": "4.57319", + "id": 294064658, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Mathéi", + "lat": "50.68481", + "lon": "4.56971", + "id": 64256 + } + ], + "matchStreet": 1 + }, + { + "name": "Vinalmont", + "names": { + "ru": "Винальмон" + }, + "lat": "50.56275", + "lon": "5.22614", + "id": 725298285, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde", + "lat": "50.55759", + "lon": "5.23247", + "id": 220734 + } + ], + "matchStreet": 1 + }, + { + "name": "Anthée", + "names": { + "wa": "Antêye" + }, + "lat": "50.23953", + "lon": "4.76041", + "id": 348123981, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Emile Collard", + "lat": "50.23974", + "lon": "4.75822", + "id": 151310 + } + ], + "matchStreet": 1 + }, + { + "name": "Belœil", + "names": { + "ru": "Белёй" + }, + "lat": "50.54792", + "lon": "3.73612", + "id": 71013315, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Paul-Emile Janson (Stambruges)", + "lat": "50.50982", + "lon": "3.71883", + "id": 194949 + }, + { + "name": "Rue Emile Carlier (Grandglise)", + "lat": "50.49852", + "lon": "3.70664", + "id": 19932 + }, + { + "name": "Rue Emile Royer (Quevaucamps)", + "lat": "50.53023", + "lon": "3.69550", + "id": 228141 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "La Glanerie", + "names": { + "ru": "Ла-Гланри" + }, + "lat": "50.52984", + "lon": "3.30103", + "id": 661173394, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Clainquart", + "lat": "50.51826", + "lon": "3.29965", + "id": 225871 + } + ], + "matchStreet": 1 + }, + { + "name": "Laeken - Laken", + "names": { + "de": "Laken", + "ru": "Лакен", + "fr": "Laeken", + "nl": "Laken" + }, + "lat": "50.88339", + "lon": "4.34872", + "id": 17428149, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Boulevard Émile Bockstael - Emile Bockstaellaan", + "names": { + "fr": "Boulevard Émile Bockstael", + "nl": "Emile Bockstaellaan" + }, + "lat": "50.87971", + "lon": "4.34777", + "id": 52214 + } + ], + "matchStreet": 1 + }, + { + "name": "Haaltert", + "names": { + "ru": "Халтерт" + }, + "lat": "50.90225", + "lon": "4.00576", + "id": 1630177961, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Burgemeester Emile De Saedeleerstraat", + "lat": "50.89614", + "lon": "3.99982", + "id": 155778 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Nessonvaux", + "names": { + "ru": "Несонво", + "wa": "Nessonvå", + "fr": "Nessonvaux" + }, + "lat": "50.57367", + "lon": "5.73714", + "id": 618759919, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Vandervelde", + "lat": "50.57363", + "lon": "5.73654", + "id": 162359, + "intersectedStreets": [ + { + "name": "Rue du Général de Gaulle", + "lat": "50.57381", + "lon": "5.73622" + }, + { + "name": "rue du Ruisseau", + "lat": "50.57373", + "lon": "5.73645" + }, + { + "name": "Grand Ventail", + "lat": "50.57386", + "lon": "5.73883" + }, + { + "name": "Rue Bourgmestre Aimé Meunier", + "lat": "50.57386", + "lon": "5.73883" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Profondeville", + "names": { + "ru": "Профондвиль", + "wa": "Parfondveye" + }, + "lat": "50.37765", + "lon": "4.86943", + "id": 265779208, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Mazy (Bois-de-Villers)", + "lat": "50.39044", + "lon": "4.80824", + "id": 185943 + }, + { + "name": "Rue Emile Mahaux", + "lat": "50.37430", + "lon": "4.83750", + "id": 186071 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Rienne", + "names": { + "ru": "Рьен", + "wa": "Riene", + "nl": "Rienne" + }, + "lat": "49.99226", + "lon": "4.88477", + "id": 678992750, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Rue Émile Montreuil", + "lat": "49.99320", + "lon": "4.88578", + "id": 160767 + } + ], + "matchStreet": 1 + }, + { + "name": "Écaussinnes", + "names": { + "ru": "Экоссинн" + }, + "lat": "50.56961", + "lon": "4.17526", + "id": 190299276, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Rue Emile Vandervelde (Cité Pro-Déo)", + "lat": "50.54214", + "lon": "4.17335", + "id": 67705 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Trooz", + "names": { + "ru": "Троз", + "wa": "Li Trô", + "fr": "Trooz" + }, + "lat": "50.57281", + "lon": "5.68836", + "id": 748734269, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Place Emile Vandervelde (Nessonvaux)", + "lat": "50.57363", + "lon": "5.73652", + "id": 162358, + "intersectedStreets": [ + { + "name": "Rue du Général de Gaulle (Nessonvaux)", + "lat": "50.57381", + "lon": "5.73619" + }, + { + "name": "rue du Ruisseau", + "lat": "50.57373", + "lon": "5.73643" + }, + { + "name": "Grand Ventail", + "lat": "50.57386", + "lon": "5.73881" + }, + { + "name": "Rue Bourgmestre Aimé Meunier", + "lat": "50.57386", + "lon": "5.73881" + } + ] + } + ], + "matchStreet": 1 + } + ] +} \ No newline at end of file diff --git a/OsmAnd-java/src/test/resources/search/street_santa_clara.json b/OsmAnd-java/src/test/resources/search/street_santa_clara.json new file mode 100644 index 0000000000..959745ebc6 --- /dev/null +++ b/OsmAnd-java/src/test/resources/search/street_santa_clara.json @@ -0,0 +1,3946 @@ +{ + "settings": { + "lat": "41.60200", + "lon": "2.62258", + "radiusLevel": 1, + "totalLimit": -1, + "lang": "", + "transliterateIfMissing": false, + "emptyQueryAllowed": false, + "sortByName": false + }, + "phrase": "santa clara", + "results": [ + "Carrer de Santa Clara, Sant Pol de Mar", + "Santa Clara, Sant Pol de Mar", + "Carrer Santa Clara, Sant Pol de Mar", + "Plaça Santa Clara, Santa Coloma de Farners", + "Carrer del Comte de Santa Clara, Barcelona", + "Carrer del Comte de Santa Clara, la Barceloneta", + "Passatge de Santa Clara, Barcelona", + "Passatge de Santa Clara, Barri Gòtic", + "Baixada de Santa Clara, Barcelona", + "Baixada de Santa Clara, Barri Gòtic", + "Carrer de Santa Clara, Girona", + "Plaça de Santa Clara, Vic", + "Carrer de Santa Clara, Poble Nou", + "Carrer de Santa Clara (Poble Nou), Sant Vicenç dels Horts", + "Carrer Vell de Santa Clara, Manresa", + "Carrer Nou de Santa Clara, Centre Històric", + "Carrer Nou de Santa Clara (Les Escodines), Manresa", + "Carrer de Santa Clara, Masquefa", + "Carrer Santa Clara, Castelló d'Empúries", + "Carrer de Santa Clara, Vilafranca del Penedès", + "Carrer de Santa Clara, Castelló d'Empúries", + "Carrer Clara Campoamor, Santa Coloma de Gramenet", + "Santa Clara" + ], + "amenities": [ + { + "name": "Pastisseria Santa Clara", + "lat": "41.38344", + "lon": "2.17755", + "id": 5954544846, + "subType": "confectionery", + "type": "shop" + }, + { + "name": "Santa Clara del Mullol", + "lat": "42.13905", + "lon": "1.12200", + "id": 865048681, + "subType": "place_of_worship", + "type": "tourism", + "additionalInfo": { + "religion_christian": "christian", + "building_type_church": "church", + "denomination_catholic": "catholic", + "wikipedia": "http://ca.wikipedia.org/wiki/Santa Clara del Mullol" + } + }, + { + "name": "Santa Clara del Mullol", + "lat": "42.13905", + "lon": "1.12200", + "id": 865048681, + "subType": "building", + "type": "man_made", + "additionalInfo": { + "religion_christian": "christian", + "building_type_church": "church", + "denomination_catholic": "catholic", + "wikipedia": "http://ca.wikipedia.org/wiki/Santa Clara del Mullol" + } + }, + { + "name": "Camping Clarà", + "lat": "41.14908", + "lon": "1.42003", + "id": 8654455984, + "subType": "camp_site", + "type": "tourism", + "additionalInfo": { + "internet_access_type_wlan": "wlan", + "phone": "977643480" + } + }, + { + "name": "Santa Clara / Muralla de Sant Magí", + "lat": "41.34637", + "lon": "1.70142", + "id": 5640608030, + "subType": "bus_stop", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "L1", + "operator": "Hispano Igualadina" + } + }, + { + "name": "Santa Clara / Muralla de Sant Magí", + "lat": "41.34637", + "lon": "1.70142", + "id": 5640608030, + "subType": "public_transport_stop_position", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "L1", + "operator": "Hispano Igualadina" + } + }, + { + "name": "Clarà", + "lat": "41.15131", + "lon": "1.41687", + "id": 8058100256, + "subType": "bus_stop", + "type": "transportation", + "additionalInfo": { + "covered_no": "no", + "bench_no": "no", + "operator": "Autocars del Penedès" + } + }, + { + "name": "Serra d'en Clarà", + "lat": "42.41337", + "lon": "2.97249", + "id": 2483176176, + "subType": "locality", + "type": "administrative" + }, + { + "name": "Jardins de Villa Clara", + "lat": "41.42300", + "lon": "2.18370", + "id": 567550629, + "subType": "park", + "type": "entertainment" + }, + { + "name": "la Vinya de Clarà", + "lat": "42.06712", + "lon": "1.80026", + "id": 740434451, + "subType": "building", + "type": "man_made" + }, + { + "name": "Santa Clara", + "names": { + "ru": "Санта-Клара", + "hu": "Santa Clara" + }, + "lat": "22.40658", + "lon": "-79.96517", + "id": -715772, + "subType": "city", + "type": "administrative", + "additionalInfo": { + "population": "216056" + } + }, + { + "name": "Santa Clara", + "lat": "21.37340", + "lon": "-89.01346", + "id": -625402, + "subType": "town", + "type": "administrative" + }, + { + "name": "Sas - Claramunt", + "lat": "41.43774", + "lon": "2.20424", + "id": 2727197818, + "subType": "bus_stop", + "type": "transportation", + "additionalInfo": { + "covered_yes": "yes", + "bench_yes": "yes", + "ref": "13", + "route_bus_ref": "N8, V33", + "operator": "TMB, TUSGSAL" + } + }, + { + "name": "Sas - Claramunt", + "lat": "41.43774", + "lon": "2.20424", + "id": 2727197818, + "subType": "public_transport_stop_position", + "type": "transportation", + "additionalInfo": { + "covered_yes": "yes", + "bench_yes": "yes", + "ref": "13", + "route_bus_ref": "N8, V33", + "operator": "TMB, TUSGSAL" + } + }, + { + "name": "Enric Clarassó - Collserola", + "lat": "41.41824", + "lon": "2.13394", + "id": 10753200842, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "124", + "operator": "TMB", + "ref": "2446" + } + }, + { + "name": "Castell de Clarà", + "lat": "41.82026", + "lon": "2.08015", + "id": 835556795, + "subType": "castle", + "type": "tourism", + "additionalInfo": { + "wikipedia": "http://ca.wikipedia.org/wiki/Castell de Clarà (Moià)" + } + }, + { + "name": "Castell de Clarà", + "lat": "41.82026", + "lon": "2.08015", + "id": 835556795, + "subType": "building", + "type": "man_made", + "additionalInfo": { + "wikipedia": "http://ca.wikipedia.org/wiki/Castell de Clarà (Moià)" + } + }, + { + "name": "Convent de Santa Clara", + "lat": "41.14429", + "lon": "1.09586", + "id": 632415625, + "subType": "place_of_worship", + "type": "tourism" + }, + { + "name": "Convent de Santa Clara", + "lat": "41.14429", + "lon": "1.09586", + "id": 632415625, + "subType": "building", + "type": "man_made" + }, + { + "name": "Riera de Clarà", + "lat": "41.56767", + "lon": "2.39547", + "id": 121597699, + "subType": "stream", + "type": "natural", + "additionalInfo": { + "intermittent": "yes" + } + }, + { + "name": "La Pobla;de Claramunt;Capellades (industrial);La Torre;de Claramunt", + "lat": "41.54382", + "lon": "1.69177", + "id": 3811621392, + "subType": "motorway_junction", + "type": "transportation", + "additionalInfo": { + "operator": "Generalitat de Catalunya" + } + }, + { + "name": "la Torre de Claramunt", + "lat": "41.53529", + "lon": "1.66093", + "id": 2920956646, + "subType": "village", + "type": "administrative", + "additionalInfo": { + "ele": "363", + "population": "1534", + "loc_name": "la Torre Alta" + } + }, + { + "name": "Can Claramunt", + "lat": "41.50662", + "lon": "1.75584", + "id": 730043780, + "subType": "hamlet", + "type": "administrative", + "additionalInfo": { + "ele": "338", + "population": "1284" + } + }, + { + "name": "Portell de Claramunt", + "lat": "42.03669", + "lon": "0.72096", + "id": 3934995340, + "subType": "saddle", + "type": "natural" + }, + { + "name": "Portell de Claramunt", + "lat": "42.03669", + "lon": "0.72096", + "id": 3934995340, + "subType": "mountain_pass", + "type": "transportation" + }, + { + "name": "La Serenitat", + "enName": "The Serenity", + "names": { + "ca": "La Serenitat", + "es": "La Serenidad" + }, + "lat": "41.37051", + "lon": "2.17235", + "id": 8255820054, + "subType": "artwork", + "type": "tourism", + "additionalInfo": { + "artwork_type_sculpture": "sculpture", + "artist_name": "Josep Clarà" + } + }, + { + "name": "Pujada de Santa Clara", + "lat": "40.81249", + "lon": "0.52438", + "id": 293763975, + "subType": "highway_steps", + "type": "transportation" + }, + { + "name": "CAP SANTA CLARA", + "lat": "41.98415", + "lon": "2.82312", + "id": 9695589798, + "subType": "clinic", + "type": "healthcare", + "openingHours": "Mo-Fr 08:00-13:45", + "additionalInfo": { + "wheelchair_yes": "yes", + "opening_hours": "Mo-Fr 08:00-13:45" + } + }, + { + "name": "CAP SANTA CLARA", + "lat": "41.98415", + "lon": "2.82312", + "id": 9695589798, + "subType": "building", + "type": "man_made", + "openingHours": "Mo-Fr 08:00-13:45", + "additionalInfo": { + "wheelchair_yes": "yes", + "opening_hours": "Mo-Fr 08:00-13:45" + } + }, + { + "name": "Clarà", + "lat": "41.98377", + "lon": "1.15425", + "id": 1248480557, + "subType": "isolated_dwelling", + "type": "administrative" + }, + { + "name": "Clarà", + "lat": "41.98377", + "lon": "1.15425", + "id": 1248480557, + "subType": "building", + "type": "man_made" + }, + { + "name": "Riera de Clarà", + "lat": "41.56762", + "lon": "2.39513", + "id": 121597689, + "subType": "stream", + "type": "natural", + "additionalInfo": { + "intermittent": "yes", + "tunnel_waterway": "yes" + } + }, + { + "name": "Riera de Clarà", + "lat": "41.56762", + "lon": "2.39513", + "id": 121597689, + "subType": "tunnel", + "type": "man_made", + "additionalInfo": { + "intermittent": "yes", + "tunnel_waterway": "yes" + } + }, + { + "name": "Roca Clara", + "lat": "42.00205", + "lon": "0.78469", + "id": 2483269388, + "subType": "locality", + "type": "administrative" + }, + { + "name": "Av. Catalana - Clara Campoamor", + "lat": "41.42416", + "lon": "2.22098", + "id": 10508165730, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "covered_no": "no", + "bench_no": "no", + "ref": "109706", + "route_bus_ref": "B20", + "operator": "Tusgsal" + } + }, + { + "name": "La Pobla;de Claramunt;Capellades (industrial);La Torre;de Claramunt", + "lat": "41.53981", + "lon": "1.69119", + "id": 3811620860, + "subType": "motorway_junction", + "type": "transportation", + "additionalInfo": { + "operator": "Generalitat de Catalunya" + } + }, + { + "name": "lo Camí de Claravalls", + "lat": "41.69330", + "lon": "1.13166", + "id": 4676221044, + "subType": "locality", + "type": "administrative" + }, + { + "name": "Água Clara", + "lat": "-20.43857", + "lon": "-52.87975", + "id": -668935, + "subType": "town", + "type": "administrative", + "additionalInfo": { + "population": "13623" + } + }, + { + "name": "L11 - Santa Clara", + "lat": "41.98301", + "lon": "2.82286", + "id": 1845638164, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "covered_no": "no", + "route_bus_ref": "L11", + "operator": "TMG" + } + }, + { + "name": "Plaça de Teresa Claramunt", + "lat": "41.27961", + "lon": "1.98256", + "id": 833025909, + "subType": "garden", + "type": "entertainment" + }, + { + "name": "Monument a Clara Campoamor", + "lat": "41.20150", + "lon": "1.63014", + "id": 4861882840, + "subType": "memorial", + "type": "tourism" + }, + { + "name": "la Pobla de Claramunt", + "lat": "41.55458", + "lon": "1.67782", + "id": 1109971453, + "subType": "railway_station", + "type": "transportation", + "additionalInfo": { + "operator": "FGC Ferrocarrils de la Generalitat de Catalunya" + } + }, + { + "name": "la Pobla de Claramunt", + "lat": "41.55458", + "lon": "1.67782", + "id": 1109971453, + "subType": "public_transport_station", + "type": "transportation", + "additionalInfo": { + "operator": "FGC Ferrocarrils de la Generalitat de Catalunya" + } + }, + { + "name": "la Pobla de Claramunt", + "lat": "41.55458", + "lon": "1.67782", + "id": 1109971453, + "subType": "building", + "type": "man_made", + "additionalInfo": { + "operator": "FGC Ferrocarrils de la Generalitat de Catalunya" + } + }, + { + "name": "Barranc de l'Aigua Clara", + "lat": "42.00002", + "lon": "0.81614", + "id": 464152703, + "subType": "stream", + "type": "natural" + }, + { + "name": "los Claramunts", + "lat": "42.02462", + "lon": "0.69776", + "id": 2483230744, + "subType": "locality", + "type": "administrative" + }, + { + "name": "Cal Claramunt", + "lat": "41.30367", + "lon": "1.64282", + "id": 433901515, + "subType": "residential", + "type": "administrative" + }, + { + "name": "Escola Santa Clara", + "lat": "41.55969", + "lon": "2.08839", + "id": 442494019, + "subType": "school", + "type": "education" + }, + { + "name": "Escola Santa Clara", + "lat": "41.55969", + "lon": "2.08839", + "id": 442494019, + "subType": "building", + "type": "man_made" + }, + { + "name": "Plana de Claravalls", + "lat": "41.68302", + "lon": "1.07932", + "id": 2483359270, + "subType": "locality", + "type": "administrative" + }, + { + "name": "Claramunt", + "lat": "41.55494", + "lon": "1.66915", + "id": 1897419834, + "subType": "survey_point", + "type": "man_made", + "additionalInfo": { + "ele": "460.80", + "note": "This is a point from an official geodetic network. Use it as a reference, but do NOT move it.", + "ref": "39163" + } + }, + { + "name": "Mas Clarà", + "lat": "42.04368", + "lon": "2.78355", + "id": 731046577, + "subType": "building", + "type": "man_made" + }, + { + "name": "Santa Clara", + "lat": "41.40345", + "lon": "2.16128", + "id": 2737036116, + "subType": "bakery", + "type": "shop" + }, + { + "name": "Riera de Clarà", + "lat": "41.55697", + "lon": "2.37290", + "id": 1274625417, + "subType": "stream", + "type": "natural" + }, + { + "name": "Santa Clara", + "lat": "41.72151", + "lon": "1.83532", + "id": 3935268030, + "subType": "bus_stop", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "L1" + } + }, + { + "name": "Santa Clara", + "lat": "41.72151", + "lon": "1.83532", + "id": 3935268030, + "subType": "public_transport_stop_position", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "L1" + } + }, + { + "name": "Riera de Clarà", + "lat": "41.55677", + "lon": "2.37273", + "id": 1274625421, + "subType": "stream", + "type": "natural", + "additionalInfo": { + "tunnel_waterway": "yes" + } + }, + { + "name": "Riera de Clarà", + "lat": "41.55677", + "lon": "2.37273", + "id": 1274625421, + "subType": "tunnel", + "type": "man_made", + "additionalInfo": { + "tunnel_waterway": "yes" + } + }, + { + "name": "Santa Clara de Olimar", + "lat": "-32.92369", + "lon": "-54.94452", + "id": -628542, + "subType": "town", + "type": "administrative", + "additionalInfo": { + "population": "2341" + } + }, + { + "name": "La Fertilitat", + "enName": "The Fertility", + "names": { + "ca": "La Fertilitat", + "es": "La Fertilidad" + }, + "lat": "41.36990", + "lon": "2.17212", + "id": 10612950182, + "subType": "artwork", + "type": "tourism", + "additionalInfo": { + "artist_name": "Josep Clarà" + } + }, + { + "name": "Escola Guillem de Claramunt", + "lat": "41.20296", + "lon": "1.27761", + "id": 525583849, + "subType": "school", + "type": "education" + }, + { + "name": "Torrent de Clarà", + "lat": "42.20050", + "lon": "1.73588", + "id": 385833345, + "subType": "stream", + "type": "natural" + }, + { + "name": "Collada de Clarà", + "lat": "42.01660", + "lon": "1.46380", + "id": 528127750, + "subType": "saddle", + "type": "natural", + "additionalInfo": { + "ele": "880" + } + }, + { + "name": "Collada de Clarà", + "lat": "42.01660", + "lon": "1.46380", + "id": 528127750, + "subType": "mountain_pass", + "type": "transportation", + "additionalInfo": { + "ele": "880" + } + }, + { + "name": "la Pobla de Claramunt", + "lat": "41.55458", + "lon": "1.67514", + "id": 4413636960256, + "subType": "village", + "type": "administrative" + }, + { + "name": "la Pobla de Claramunt", + "lat": "41.55458", + "lon": "1.67514", + "id": 4413636960256, + "subType": "residential", + "type": "administrative" + }, + { + "name": "CAP Santa Clara", + "lat": "41.98420", + "lon": "2.82297", + "id": 4400118235136, + "subType": "clinic", + "type": "healthcare" + }, + { + "name": "CAP Santa Clara", + "lat": "41.98420", + "lon": "2.82297", + "id": 4400118235136, + "subType": "building", + "type": "man_made" + }, + { + "name": "Claverol / Santa Clara", + "lat": "41.38642", + "lon": "2.01052", + "id": 6861741690, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "e20", + "operator": "Soler i Sauret, Soler i Sauret, S.A." + } + }, + { + "name": "Santa Clara", + "lat": "41.98238", + "lon": "2.82278", + "id": 6879115086, + "subType": "florist", + "type": "shop" + }, + { + "name": "Estany de Clarà", + "lat": "41.14817", + "lon": "1.42067", + "id": 463745197, + "subType": "water", + "type": "natural" + }, + { + "name": "Convent de Santa Clara", + "lat": "42.25891", + "lon": "3.07639", + "id": 4413972850688, + "subType": "monument", + "type": "tourism" + }, + { + "name": "Convent de Santa Clara", + "lat": "42.25891", + "lon": "3.07639", + "id": 4413972850688, + "subType": "building", + "type": "man_made" + }, + { + "name": "Claverol / Santa Clara", + "lat": "41.38644", + "lon": "2.01056", + "id": 11175822636, + "subType": "bus_stop", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "e20", + "operator": "Soler i Sauret" + } + }, + { + "name": "Claverol / Santa Clara", + "lat": "41.38644", + "lon": "2.01056", + "id": 11175822636, + "subType": "public_transport_stop_position", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "e20", + "operator": "Soler i Sauret" + } + }, + { + "name": "Plaça de Clara Campoamor", + "lat": "41.20141", + "lon": "1.63029", + "id": 410285817, + "subType": "park", + "type": "entertainment" + }, + { + "name": "Clarà", + "lat": "41.15118", + "lon": "1.41700", + "id": 8058100254, + "subType": "bus_stop", + "type": "transportation", + "additionalInfo": { + "covered_no": "no", + "bench_no": "no", + "operator": "Autocars del Penedès" + } + }, + { + "name": "Santa Clara", + "lat": "41.73371", + "lon": "2.95099", + "id": 11732311074, + "subType": "cafe", + "type": "sustenance" + }, + { + "name": "Casa Clara", + "lat": "41.45725", + "lon": "2.25224", + "id": 187813925, + "subType": "building", + "type": "man_made" + }, + { + "name": "Parcel·les Clarasó", + "lat": "41.14796", + "lon": "1.09232", + "id": 803598451, + "subType": "residential", + "type": "administrative" + }, + { + "name": "Rosa Leveroni - Teresa Claramunt", + "lat": "41.38594", + "lon": "2.05929", + "id": 10829545194, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "L30", + "operator": "Rosanbus", + "ref": "109953" + } + }, + { + "name": "Sas - Claramunt", + "lat": "41.43774", + "lon": "2.20415", + "id": 10510567904, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "covered_yes": "yes", + "bench_yes": "yes", + "ref": "13", + "route_bus_ref": "V33", + "operator": "TMB" + } + }, + { + "name": "Pont de Clarà", + "lat": "42.06679", + "lon": "1.79620", + "id": 716008887, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "bridge_car": "yes", + "route_bus_ref": "777", + "ref": "C-26" + } + }, + { + "name": "L11 - Santa Clara", + "lat": "41.98503", + "lon": "2.82342", + "id": 1404566956, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "covered_no": "no", + "tactile_paving_no": "no", + "bench_no": "no", + "route_bus_ref": "L11", + "operator": "TMG" + } + }, + { + "name": "Clarà", + "lat": "42.34992", + "lon": "3.12125", + "id": 2483196000, + "subType": "locality", + "type": "administrative" + }, + { + "name": "Plaça del Claramunt", + "lat": "41.57170", + "lon": "1.64291", + "id": 1053371799, + "subType": "park", + "type": "entertainment" + }, + { + "name": "Casa Clara", + "lat": "42.25887", + "lon": "3.07606", + "id": 3419684236, + "subType": "guest_house", + "type": "tourism" + }, + { + "name": "Ca la Clara", + "lat": "41.39185", + "lon": "1.67001", + "id": 8283628062, + "subType": "isolated_dwelling", + "type": "administrative" + }, + { + "name": "Clara-Hilda-Felsen", + "lat": "50.14099", + "lon": "8.38184", + "id": -168805, + "subType": "locality", + "type": "administrative" + }, + { + "name": "Plans de Can Claramunt", + "lat": "41.53155", + "lon": "1.89094", + "id": 2483099238, + "subType": "locality", + "type": "administrative" + }, + { + "name": "Passatge de Mossén Félix Clara", + "lat": "41.23632", + "lon": "1.81004", + "id": 476380099, + "subType": "highway_steps", + "type": "transportation" + }, + { + "name": "Escultor Clarà", + "lat": "41.44922", + "lon": "2.22662", + "id": 747927549, + "subType": "parking", + "type": "transportation", + "additionalInfo": { + "operator": "Engestur" + } + }, + { + "name": "Biblioteca Clarà", + "lat": "41.39977", + "lon": "2.12959", + "id": 339649943, + "subType": "library", + "type": "education", + "openingHours": "Mo-Tu 15:30-20:30; We-Th 10:00-14:00, 15:30-20:30; Fr 15:30-20:30; Sa 10:00-14:00", + "additionalInfo": { + "opening_hours": "Mo-Tu 15:30-20:30; We-Th 10:00-14:00, 15:30-20:30; Fr 15:30-20:30; Sa 10:00-14:00", + "website": "http://www.barcelona.cat/bibclara", + "phone": "+34932801547", + "email": "b.barcelona.cl@diba.cat" + } + }, + { + "name": "Juan Rodríguez Clara", + "lat": "17.99275", + "lon": "-95.40399", + "id": -649830, + "subType": "town", + "type": "administrative" + }, + { + "name": "Pujada de Santa Clara", + "lat": "40.81243", + "lon": "0.52430", + "id": 293754605, + "subType": "highway_steps", + "type": "transportation" + }, + { + "name": "Castell de Claravalls", + "lat": "41.70273", + "lon": "1.12633", + "id": 4654764994, + "subType": "castle", + "type": "tourism" + }, + { + "name": "Camí de Can Claramunt", + "lat": "41.30515", + "lon": "1.64327", + "id": 304550037, + "subType": "bridge", + "type": "man_made", + "additionalInfo": { + "bicycle_yes": "yes", + "bridge_car": "yes" + } + }, + { + "name": "Claramunt", + "lat": "42.17865", + "lon": "0.78269", + "id": 1803024068, + "subType": "village", + "type": "administrative", + "additionalInfo": { + "website": "http://www.ajuntamentdetremp.cat/ca/viure-a-tremp/nuclis-agregats/claramunt", + "ele": "1095", + "population": "1", + "wikipedia": "http://ca.wikipedia.org/wiki/Claramunt" + } + }, + { + "name": "Plaça Beatriu de Claramunt", + "lat": "41.22341", + "lon": "1.71415", + "id": 238406017, + "subType": "square", + "type": "man_made", + "additionalInfo": { + "lit_yes": "yes" + } + }, + { + "name": "Biblioteca Clarà", + "lat": "41.39967", + "lon": "2.12967", + "id": 339649929, + "subType": "building", + "type": "man_made" + }, + { + "name": "Claravalls", + "lat": "41.70230", + "lon": "1.12676", + "id": 4654765000, + "subType": "village", + "type": "administrative", + "additionalInfo": { + "website": "http://www.tarrega.cat/m_claravalls.html", + "population": "149", + "wikipedia": "http://ca.wikipedia.org/wiki/Claravalls" + } + }, + { + "name": "Av. de la Roureda - Escultor Clarà", + "lat": "41.31998", + "lon": "2.00782", + "id": 10450149214, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "L86, N14", + "operator": "Mohn", + "ref": "104769" + } + }, + { + "name": "Sant Andreu de Clarà", + "lat": "42.00152", + "lon": "1.45356", + "id": 627599319, + "subType": "place_of_worship", + "type": "tourism", + "additionalInfo": { + "religion_christian": "christian", + "denomination_roman_catholic": "roman_catholic" + } + }, + { + "name": "Sant Andreu de Clarà", + "lat": "42.00152", + "lon": "1.45356", + "id": 627599319, + "subType": "building", + "type": "man_made", + "additionalInfo": { + "religion_christian": "christian", + "denomination_roman_catholic": "roman_catholic" + } + }, + { + "name": "Monestir de Santa Clara", + "lat": "42.26054", + "lon": "3.04780", + "id": 1058163091, + "subType": "place_of_worship", + "type": "tourism", + "additionalInfo": { + "religion_christian": "christian", + "denomination_catholic": "catholic", + "website": "http://www.clarissesfortia.cat/" + } + }, + { + "name": "Monestir de Santa Clara", + "lat": "42.26054", + "lon": "3.04780", + "id": 1058163091, + "subType": "building", + "type": "man_made", + "additionalInfo": { + "religion_christian": "christian", + "denomination_catholic": "catholic", + "website": "http://www.clarissesfortia.cat/" + } + }, + { + "name": "Masia de Clara", + "lat": "41.99348", + "lon": "0.77533", + "id": 6794112822, + "subType": "isolated_dwelling", + "type": "administrative" + }, + { + "name": "Sas - Claramunt", + "lat": "41.43769", + "lon": "2.20417", + "id": 10318316416, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "N8, 60", + "operator": "TMB, TUSGSAL", + "ref": "13" + } + }, + { + "name": "Fruites Claramunt", + "lat": "41.23362", + "lon": "1.73809", + "id": 8593845122, + "subType": "greengrocer", + "type": "shop", + "additionalInfo": { + "website": "http://www.fruitesclaramunt.com", + "phone": "938932704", + "email": "gerencia@fruitesclaramunt.com" + } + }, + { + "name": "Águas Claras", + "lat": "-15.84199", + "lon": "-48.02813", + "id": -695185, + "subType": "city", + "type": "administrative", + "additionalInfo": { + "population": "135685" + } + }, + { + "name": "Puig Clarà", + "lat": "42.13279", + "lon": "2.73828", + "id": 6418222344, + "subType": "peak", + "type": "natural", + "additionalInfo": { + "ele": "315", + "wikipedia": "http://ca.wikipedia.org/wiki/Puig Clarà" + } + }, + { + "name": "Riera de Clarà", + "lat": "41.56343", + "lon": "2.38547", + "id": 401272355, + "subType": "stream", + "type": "natural" + }, + { + "name": "Puig Clarà (Panoràmica del Pirineu)", + "lat": "42.13286", + "lon": "2.73828", + "id": 6418222342, + "subType": "viewpoint", + "type": "tourism" + }, + { + "name": "Puig Clarà (Panoràmica de l'Estany)", + "lat": "42.13262", + "lon": "2.73871", + "id": 6418222340, + "subType": "viewpoint", + "type": "tourism" + }, + { + "name": "Castell de Claramunt", + "lat": "41.55502", + "lon": "1.66924", + "id": 526389675, + "subType": "castle", + "type": "tourism", + "additionalInfo": { + "wikipedia": "http://ca.wikipedia.org/wiki/Castell de Claramunt" + } + }, + { + "name": "Castell de Claramunt", + "lat": "41.55502", + "lon": "1.66924", + "id": 526389675, + "subType": "building", + "type": "man_made", + "additionalInfo": { + "wikipedia": "http://ca.wikipedia.org/wiki/Castell de Claramunt" + } + }, + { + "name": "Xiringuito Clara", + "lat": "41.11885", + "lon": "1.27422", + "id": 1722045680, + "subType": "bar", + "type": "sustenance" + }, + { + "name": "Pl. Santa Clara", + "lat": "41.93107", + "lon": "2.25542", + "id": 6442740362, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "covered_yes": "yes", + "bench_yes": "yes", + "route_bus_ref": "L-1, L-2, L3, L8", + "operator": "Sagalés" + } + }, + { + "name": "Av. Catalana - Clara Campoamor", + "lat": "41.42434", + "lon": "2.22087", + "id": 11219586540, + "subType": "bus_stop", + "type": "transportation", + "additionalInfo": { + "covered_no": "no", + "bench_no": "no", + "ref": "109700", + "route_bus_ref": "B20", + "operator": "Tusgsal" + } + }, + { + "name": "Av. Catalana - Clara Campoamor", + "lat": "41.42434", + "lon": "2.22087", + "id": 11219586540, + "subType": "public_transport_stop_position", + "type": "transportation", + "additionalInfo": { + "covered_no": "no", + "bench_no": "no", + "ref": "109700", + "route_bus_ref": "B20", + "operator": "Tusgsal" + } + }, + { + "name": "Clarassó", + "lat": "41.14754", + "lon": "1.09185", + "id": 6797558650, + "subType": "bus_stop", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "L20", + "operator": "Reus Transport", + "ref": "32" + } + }, + { + "name": "Clarassó", + "lat": "41.14754", + "lon": "1.09185", + "id": 6797558650, + "subType": "public_transport_stop_position", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "L20", + "operator": "Reus Transport", + "ref": "32" + } + }, + { + "name": "Serra d'en Clarà", + "lat": "41.29833", + "lon": "1.11417", + "id": 2483582362, + "subType": "locality", + "type": "administrative" + }, + { + "name": "Av. Catalana - Clara Campoamor", + "lat": "41.42421", + "lon": "2.22102", + "id": 11194748624, + "subType": "bus_stop", + "type": "transportation", + "additionalInfo": { + "covered_no": "no", + "bench_no": "no", + "ref": "109706", + "route_bus_ref": "B20", + "operator": "Tusgsal" + } + }, + { + "name": "Av. Catalana - Clara Campoamor", + "lat": "41.42421", + "lon": "2.22102", + "id": 11194748624, + "subType": "public_transport_stop_position", + "type": "transportation", + "additionalInfo": { + "covered_no": "no", + "bench_no": "no", + "ref": "109706", + "route_bus_ref": "B20", + "operator": "Tusgsal" + } + }, + { + "name": "Restaurant Santa Clara", + "lat": "42.05465", + "lon": "3.20520", + "id": 6547212138, + "subType": "restaurant", + "type": "sustenance", + "additionalInfo": { + "cuisine_regional": "regional" + } + }, + { + "name": "Clarà", + "lat": "42.11861", + "lon": "2.12013", + "id": 11086621508, + "subType": "historic_ruins", + "type": "tourism" + }, + { + "name": "Apartaments Rosa Clara", + "lat": "41.70132", + "lon": "2.84823", + "id": 8424184200, + "subType": "guest_house", + "type": "tourism" + }, + { + "name": "Carrer Nou de Santa Clara", + "lat": "40.81219", + "lon": "0.52340", + "id": 293754645, + "subType": "highway_steps", + "type": "transportation" + }, + { + "name": "Hostal Santa Clara", + "lat": "42.05473", + "lon": "3.20522", + "id": 641447779, + "subType": "guest_house", + "type": "tourism", + "additionalInfo": { + "internet_access_fee_no": "no", + "internet_access_type_wlan": "wlan", + "website": "http://www.hostalsantaclara.com/" + } + }, + { + "name": "Hostal Santa Clara", + "lat": "42.05473", + "lon": "3.20522", + "id": 641447779, + "subType": "building", + "type": "man_made", + "additionalInfo": { + "internet_access_fee_no": "no", + "internet_access_type_wlan": "wlan", + "website": "http://www.hostalsantaclara.com/" + } + }, + { + "name": "Clarà", + "lat": "42.00280", + "lon": "1.45721", + "id": 3352724304, + "subType": "hamlet", + "type": "administrative", + "additionalInfo": { + "wikipedia": "http://ca.wikipedia.org/wiki/Clarà (Castellar de la Ribera)" + } + }, + { + "name": "Molí de Clarà", + "lat": "42.11685", + "lon": "2.11622", + "id": 11086126678, + "subType": "building", + "type": "man_made" + }, + { + "name": "Clot de Claravalls", + "lat": "41.70515", + "lon": "1.14762", + "id": 4675833070, + "subType": "locality", + "type": "administrative" + }, + { + "name": "M. Clara Pujadas i Gil", + "lat": "41.44168", + "lon": "2.22111", + "id": 5903020870, + "subType": "pharmacy", + "type": "healthcare", + "openingHours": "Mo-Fr 09:00-14:00,16:30-20:30;Sa 09:00-14:00", + "additionalInfo": { + "wheelchair_limited": "limited", + "opening_hours": "Mo-Fr 09:00-14:00,16:30-20:30;Sa 09:00-14:00", + "phone": "+34933882482", + "wheelchair_description": "Porta no adaptada" + } + }, + { + "name": "Monestir de Santa Clara", + "lat": "41.72034", + "lon": "1.83555", + "id": 4404046389248, + "subType": "place_of_worship", + "type": "tourism", + "additionalInfo": { + "religion_christian": "christian", + "denomination_catholic": "catholic" + } + }, + { + "name": "Monestir de Santa Clara", + "lat": "41.72034", + "lon": "1.83555", + "id": 4404046389248, + "subType": "building", + "type": "man_made", + "additionalInfo": { + "religion_christian": "christian", + "denomination_catholic": "catholic" + } + }, + { + "name": "Balma Clara", + "lat": "41.64691", + "lon": "2.02335", + "id": 3527915368, + "subType": "cave_entrance", + "type": "natural" + }, + { + "name": "Iglesia de San Sadurní de Clará", + "names": { + "ca": "Església de Sant Serni de Clarà", + "es": "Iglesia de San Sadurní de Clará" + }, + "lat": "42.06500", + "lon": "1.80122", + "id": 1184780863, + "subType": "place_of_worship", + "type": "tourism", + "additionalInfo": { + "religion_christian": "christian", + "denomination_roman_catholic": "roman_catholic", + "wikipedia": "http://es.wikipedia.org/wiki/Es:Iglesia_de_San_Sadurní_de_Clará" + } + }, + { + "name": "Iglesia de San Sadurní de Clará", + "names": { + "ca": "Església de Sant Serni de Clarà", + "es": "Iglesia de San Sadurní de Clará" + }, + "lat": "42.06500", + "lon": "1.80122", + "id": 1184780863, + "subType": "building", + "type": "man_made", + "additionalInfo": { + "religion_christian": "christian", + "denomination_roman_catholic": "roman_catholic", + "wikipedia": "http://es.wikipedia.org/wiki/Es:Iglesia_de_San_Sadurní_de_Clará" + } + }, + { + "name": "Mas Clarà", + "lat": "41.98880", + "lon": "2.79967", + "id": 4400107862016, + "subType": "building", + "type": "man_made" + }, + { + "name": "Plaça de Teresa Claramunt", + "lat": "41.36288", + "lon": "2.13974", + "id": 562546723, + "subType": "park", + "type": "entertainment" + }, + { + "name": "Riera de Clarà", + "lat": "41.55540", + "lon": "2.36700", + "id": 457500271, + "subType": "stream", + "type": "natural" + }, + { + "name": "Claravalls", + "lat": "41.70268", + "lon": "1.12573", + "id": 447769179, + "subType": "residential", + "type": "administrative" + }, + { + "name": "Ateneu Carme Claramunt", + "lat": "41.44236", + "lon": "2.22598", + "id": 7112179200, + "subType": "political_party", + "type": "office", + "additionalInfo": { + "wheelchair_no": "no" + } + }, + { + "name": "Av. Catalana - Clara Campoamor", + "lat": "41.42437", + "lon": "2.22093", + "id": 10508165732, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "covered_no": "no", + "bench_no": "no", + "ref": "109700", + "route_bus_ref": "B20", + "operator": "Tusgsal" + } + }, + { + "name": "Mare de Déu del Port - Pl Teresa Claramunt", + "lat": "41.36228", + "lon": "2.13952", + "id": 10686664968, + "subType": "public_transport_platform", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "125", + "operator": "TMB", + "ref": "2493" + } + }, + { + "name": "Clara", + "names": { + "ru": "Клара", + "ga": "Clóirtheach" + }, + "lat": "53.34251", + "lon": "-7.61389", + "id": -695482, + "subType": "town", + "type": "administrative", + "additionalInfo": { + "population": "3242" + } + }, + { + "name": "Plaça de la Mare Santa Clara", + "lat": "41.61315", + "lon": "0.62223", + "id": 673912799, + "subType": "square", + "type": "man_made" + }, + { + "name": "Panteón Silva Pérez y Antonio Leal Da Rosa", + "names": { + "es": "Panteón Silva Pérez y Antonio Leal Da Rosa" + }, + "lat": "41.35270", + "lon": "2.15235", + "id": 9767364056, + "subType": "memorial", + "type": "tourism", + "additionalInfo": { + "architecture_modern": "modern", + "architect_name": "Leandre Albareda i Petit; Enric Clarasó i Daudí" + } + }, + { + "name": "La Sínia de Santa Clara", + "lat": "41.72186", + "lon": "1.83470", + "id": 8135269918, + "subType": "cafe", + "type": "sustenance" + }, + { + "name": "Av. Navarra - Av. Clarasó", + "lat": "41.23141", + "lon": "1.78897", + "id": 4912393974, + "subType": "bus_stop", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "L2", + "operator": "TCC" + } + }, + { + "name": "Av. Navarra - Av. Clarasó", + "lat": "41.23141", + "lon": "1.78897", + "id": 4912393974, + "subType": "public_transport_stop_position", + "type": "transportation", + "additionalInfo": { + "route_bus_ref": "L2", + "operator": "TCC" + } + }, + { + "name": "La Deessa", + "enName": "The Goddess", + "names": { + "it": "La Dea", + "fr": "La Déesse", + "pdc": "Die Göttin", + "ca": "La Deessa", + "es": "La Diosa" + }, + "lat": "41.38644", + "lon": "2.17010", + "id": 10809276276, + "subType": "artwork", + "type": "tourism", + "additionalInfo": { + "artwork_type_sculpture": "sculpture", + "start_date": "1927", + "artist_name": "Josep Clarà i Ayats", + "alt_name": "L'enigma" + } + }, + { + "name": "Panteón Lluisa Denís i Reverter y Santiago Rusiñol", + "names": { + "es": "Panteón Lluisa Denís i Reverter y Santiago Rusiñol" + }, + "lat": "41.35275", + "lon": "2.15197", + "id": 9767364054, + "subType": "memorial", + "type": "tourism", + "additionalInfo": { + "architect_name": "Enric Clarasó i Daudí; Miquel Font\"" + } + }, + { + "name": "345 - Pl. Teresa de Claramunt", + "lat": "41.36312", + "lon": "2.13976", + "id": 7719459490, + "subType": "bicycle_rental", + "type": "service", + "additionalInfo": { + "ele": "48", + "ref": "345", + "capacity": "31", + "operator": "Clear Channel", + "network": "bicing" + } + }, + { + "name": "Clarà", + "lat": "42.12143", + "lon": "1.05763", + "id": 2483255490, + "subType": "locality", + "type": "administrative" + }, + { + "name": "Pla de Clarà", + "lat": "42.13981", + "lon": "1.88411", + "id": 2483106502, + "subType": "locality", + "type": "administrative" + }, + { + "name": "la Pobla de Claramunt", + "lat": "41.55519", + "lon": "1.67473", + "id": 2920956126, + "subType": "village", + "type": "administrative", + "additionalInfo": { + "website": "http://www.lapobladeclaramunt.cat", + "ele": "265", + "population": "2295", + "wikipedia": "http://ca.wikipedia.org/wiki/La Pobla de Claramunt" + } + }, + { + "name": "la Pobla de Claramunt", + "lat": "41.55465", + "lon": "1.67790", + "id": 781818710, + "subType": "public_transport_stop_position", + "type": "transportation", + "additionalInfo": { + "route_train_ref": "R6, R60", + "operator": "FGC Ferrocarrils de la Generalitat de Catalunya" + } + }, + { + "name": "Cementiri de la Pobla de Claramunt", + "lat": "41.56495", + "lon": "1.66977", + "id": 467279127, + "subType": "grave_yard", + "type": "tourism" + }, + { + "name": "Santa Maria del Castell de Claramunt", + "lat": "41.55487", + "lon": "1.66960", + "id": 526389869, + "subType": "historic_ruins", + "type": "tourism" + }, + { + "name": "Santa Maria del Castell de Claramunt", + "lat": "41.55487", + "lon": "1.66960", + "id": 526389869, + "subType": "building", + "type": "man_made" + }, + { + "name": "Plaça de Santa Clara", + "lat": "41.93107", + "lon": "2.25546", + "id": 92686665, + "subType": "square", + "type": "man_made" + }, + { + "name": "Plaça de Clara Campoamor", + "lat": "41.62731", + "lon": "0.63287", + "id": 672301789, + "subType": "square", + "type": "man_made" + }, + { + "name": "Santa Clara", + "lat": "13.70230", + "lon": "-88.72792", + "id": -639707, + "subType": "town", + "type": "administrative" + }, + { + "name": "Càmping Clarà", + "lat": "41.14895", + "lon": "1.41998", + "id": 463775793, + "subType": "camp_site", + "type": "tourism" + }, + { + "name": "Riera de Clarà", + "lat": "42.04000", + "lon": "1.82551", + "id": 568585721, + "subType": "stream", + "type": "natural" + }, + { + "name": "Passatge de Santa Clara", + "lat": "41.38389", + "lon": "2.17735", + "id": 109689429, + "subType": "tunnel", + "type": "man_made", + "additionalInfo": { + "access_private": "private", + "tunnel_pedestrian": "yes" + } + }, + { + "name": "Josep Clarà", + "lat": "41.35363", + "lon": "2.15429", + "id": 4696518130, + "subType": "memorial", + "type": "tourism" + } + ], + "cities": [ + { + "name": "Sant Quintí de Mediona", + "lat": "41.46279", + "lon": "1.66522", + "id": 387428854, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrer de Clara Campoamor", + "lat": "41.46545", + "lon": "1.66245", + "id": 83553, + "intersectedStreets": [ + { + "name": "Carrer de la Cova", + "lat": "41.46545", + "lon": "1.66245" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "la Barceloneta", + "names": { + "ru": "Барселонета" + }, + "lat": "41.38066", + "lon": "2.18993", + "id": 1433944928, + "type": "SUBURB", + "listOfStreets": [ + { + "name": "Carrer del Comte de Santa Clara", + "lat": "41.37831", + "lon": "2.18928", + "id": 13101 + } + ], + "matchStreet": 1 + }, + { + "name": "Cambrils", + "lat": "41.06794", + "lon": "1.06576", + "id": 1470838172, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de Josep Clarà", + "lat": "41.08060", + "lon": "1.09483", + "id": 68695 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "la Bisbal d'Empordà", + "lat": "41.95987", + "lon": "3.03961", + "id": 1470838432, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer Josep Vila Clara", + "lat": "41.95585", + "lon": "3.04461", + "id": 60290, + "intersectedStreets": [ + { + "name": "Avinguda de Josep Irla", + "lat": "41.95675", + "lon": "3.04429" + }, + { + "name": "Carrer de Ramon Muntaner", + "lat": "41.95587", + "lon": "3.04461" + }, + { + "name": "Camí de Guardiola", + "lat": "41.95501", + "lon": "3.04491" + } + ] + }, + { + "name": "Carrer Mas Clara", + "lat": "41.96522", + "lon": "3.03965", + "id": 82833, + "intersectedStreets": [ + { + "name": "Carrer Agustí Font", + "lat": "41.96468", + "lon": "3.03847" + }, + { + "name": "Carrer Paral·lel", + "lat": "41.96468", + "lon": "3.03847" + }, + { + "name": "Carrer Jofre el Pilós", + "lat": "41.96522", + "lon": "3.03965" + }, + { + "name": "Carrer Jaume II", + "lat": "41.96568", + "lon": "3.04057" + }, + { + "name": "Carretera Castell d'Empordà", + "lat": "41.96568", + "lon": "3.04057" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Sitges", + "lat": "41.23667", + "lon": "1.82281", + "id": 1470839456, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Passatge de Mossèn Fèlix Clarà", + "lat": "41.23587", + "lon": "1.80989", + "id": 3637, + "buildings": [], + "intersectedStreets": [ + { + "name": "Carrer d'en Pau Barrabeig", + "lat": "41.23567", + "lon": "1.80995" + }, + { + "name": "Carrer de la Carreta", + "lat": "41.23562", + "lon": "1.81013" + }, + { + "name": "Carrer Major", + "lat": "41.23633", + "lon": "1.81004" + }, + { + "name": "Carrer de l'Hort de Can Falç", + "lat": "41.23612", + "lon": "1.80993" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Montbau", + "lat": "41.43190", + "lon": "2.14160", + "id": 3170159053, + "type": "SUBURB", + "listOfStreets": [ + { + "name": "Carrer de Clarà Ayats", + "lat": "41.43085", + "lon": "2.14128", + "id": 73840, + "buildings": [], + "intersectedStreets": [ + { + "name": "Carrer de Roig i Solé", + "lat": "41.43087", + "lon": "2.14094" + }, + { + "name": "Carrer de la Pintura", + "lat": "41.43085", + "lon": "2.14128" + }, + { + "name": "Carrer de la Música", + "lat": "41.43176", + "lon": "2.14167" + }, + { + "name": "Carrer de la Poesia", + "lat": "41.43254", + "lon": "2.14203" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Lliçà d'Amunt", + "lat": "41.61049", + "lon": "2.23971", + "id": 395368558, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrer de Josep Clarà (Ca l'Esteper)", + "lat": "41.61095", + "lon": "2.22164", + "id": 33511, + "intersectedStreets": [ + { + "name": "Carrer de Frederic Marès (Ca l'Esteper)", + "lat": "41.61057", + "lon": "2.22136" + }, + { + "name": "Carrer de Marià Fortuny (Ca l'Esteper)", + "lat": "41.61196", + "lon": "2.22297" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Girona", + "names": { + "be": "Херона", + "ru": "Жирона", + "ko": "지로나", + "pt": "Gerunda", + "gl": "Xirona", + "el": "Ζιρόνα", + "lt": "Cherona", + "fr": "Gérone", + "an": "Chirona", + "es": "Gerona", + "zh": "赫罗纳", + "eu": "Girona", + "ar": "جرندة", + "uk": "Жірона", + "ja": "ジローナ", + "fa": "خرنا", + "he": "ז'ירונה", + "ca": "Girona", + "nl": "Gerona", + "sr": "Ђирона" + }, + "lat": "41.97930", + "lon": "2.81995", + "id": 30894545, + "type": "CITY", + "listOfStreets": [ + { + "name": "Carrer de Josep Clarà i Ayats", + "names": { + "ca": "Carrer de Josep Clarà i Ayats" + }, + "lat": "41.97162", + "lon": "2.82853", + "id": 8256, + "buildings": [], + "intersectedStreets": [ + { + "name": "Carrer de Carles de Bolós", + "names": { + "ca": "Carrer de Carles de Bolós" + }, + "lat": "41.97168", + "lon": "2.82846" + }, + { + "name": "Carrer de Joan Coromines i Vigneaux (Disseminat de Montilivi)", + "names": { + "ca": "Carrer de Joan Coromines i Vigneaux (Disseminat de Montilivi)" + }, + "lat": "41.97168", + "lon": "2.82846" + } + ] + }, + { + "name": "Carrer de Santa Clara", + "names": { + "ca": "Carrer de Santa Clara" + }, + "lat": "41.98495", + "lon": "2.82336", + "id": 586 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Roquetes", + "names": { + "ca": "Roquetes", + "es": "Roquetas" + }, + "lat": "40.82037", + "lon": "0.50344", + "id": 1460477958, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrer Santa Clara", + "lat": "40.82056", + "lon": "0.50520", + "id": 11644 + } + ], + "matchStreet": 1 + }, + { + "name": "Palafrugell", + "lat": "41.91837", + "lon": "3.16198", + "id": 1470838631, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de Josep Clarà (Tamariu)", + "lat": "41.91745", + "lon": "3.20432", + "id": 69144, + "intersectedStreets": [ + { + "name": "Carrer de Berruguete", + "lat": "41.91750", + "lon": "3.20427" + }, + { + "name": "Carrer de Carles Buïgas (Tamariu)", + "lat": "41.91750", + "lon": "3.20427" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Esparreguera", + "lat": "41.54065", + "lon": "1.86924", + "id": 1460478465, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de Josep Clarà", + "lat": "41.52710", + "lon": "1.84793", + "id": 111053, + "intersectedStreets": [ + { + "name": "Carrer de Carles Buïgas", + "lat": "41.52803", + "lon": "1.84725" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Sant Just Desvern", + "names": { + "es": "San Justo Desvern" + }, + "lat": "41.38158", + "lon": "2.07506", + "id": 1470838887, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de Clara Campoamor", + "lat": "41.38657", + "lon": "2.06114", + "id": 73250, + "intersectedStreets": [ + { + "name": "Carrer de Rosa Leveroni", + "lat": "41.38657", + "lon": "2.06114" + }, + { + "name": "carrer d'Emília Guàrdia", + "lat": "41.38657", + "lon": "2.06114" + }, + { + "name": "Passeig del Camí de Can Gelabert", + "lat": "41.38581", + "lon": "2.06163" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Santa Coloma de Gramenet", + "names": { + "ru": "Санта-Колома-де-Граменет" + }, + "lat": "41.45156", + "lon": "2.20834", + "id": 346353020, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer Clara Campoamor", + "lat": "41.45813", + "lon": "2.19546", + "id": 113362 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Tarragona", + "names": { + "be": "Тарагона", + "ru": "Таррагона", + "ko": "타라고나", + "el": "Ταρραγόνα", + "lt": "Taragona", + "fr": "Tarragone", + "es": "Tarragona", + "zh": "塔拉戈纳", + "ar": "طراغونة", + "la": "Tarraco", + "uk": "Таррагона", + "ja": "タラゴナ", + "fa": "تاراگونا", + "he": "טרגונה", + "sr": "Тарагона" + }, + "lat": "41.11724", + "lon": "1.25461", + "id": 152377920, + "type": "CITY", + "listOfStreets": [ + { + "name": "Carrer de Clara Campoamor Rodríguez", + "lat": "41.13584", + "lon": "1.29675", + "id": 38178 + }, + { + "name": "Carrer de Santa Clara", + "lat": "41.11554", + "lon": "1.25678", + "id": 16454 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Viladecans", + "lat": "41.31631", + "lon": "2.01560", + "id": 2375518653, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de l'Escultor Clarà", + "lat": "41.31940", + "lon": "2.00788", + "id": 41627, + "intersectedStreets": [ + { + "name": "Avinguda Roureda", + "lat": "41.31970", + "lon": "2.00749" + }, + { + "name": "Carrer Joaquim Mir", + "lat": "41.31940", + "lon": "2.00788" + }, + { + "name": "Carrer Pere Sala", + "lat": "41.31908", + "lon": "2.00827" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Ca l'Esteper", + "lat": "41.61069", + "lon": "2.22201", + "id": 926958321, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Carrer de Josep Clarà", + "lat": "41.61097", + "lon": "2.22164", + "id": 33512, + "intersectedStreets": [ + { + "name": "Carrer de Frederic Marès", + "lat": "41.61058", + "lon": "2.22136" + }, + { + "name": "Carrer de Marià Fortuny", + "lat": "41.61198", + "lon": "2.22297" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Tàrrega", + "names": { + "es": "Tárrega" + }, + "lat": "41.64728", + "lon": "1.14090", + "id": 1470839515, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de Santa Clara", + "lat": "41.64622", + "lon": "1.13550", + "id": 40000 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Santa Coloma de Farners", + "lat": "41.86031", + "lon": "2.66590", + "id": 1470839251, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Plaça Santa Clara", + "lat": "41.86809", + "lon": "2.65985", + "id": 22434 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Tortosa", + "lat": "40.81102", + "lon": "0.52093", + "id": 1470839509, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de Santa Clara", + "lat": "40.81201", + "lon": "0.52355", + "id": 11129 + }, + { + "name": "Carrer Nou de Santa Clara", + "lat": "40.81220", + "lon": "0.52365", + "id": 11130 + }, + { + "name": "Pujada de Santa Clara", + "lat": "40.81243", + "lon": "0.52430", + "id": 54874 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Cardedeu", + "names": { + "ar": "كارديديو", + "uk": "Кардазеу", + "el": "Καρδεδέου" + }, + "lat": "41.63851", + "lon": "2.35584", + "id": 1470838185, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer del Escultor Clarà", + "lat": "41.64439", + "lon": "2.35545", + "id": 46009, + "intersectedStreets": [ + { + "name": "Passeig Font dels Oms", + "lat": "41.64450", + "lon": "2.35502" + }, + { + "name": "Carrer de la Penya", + "lat": "41.64439", + "lon": "2.35545" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Sant Vicenç dels Horts", + "lat": "41.39342", + "lon": "2.00994", + "id": 1470839216, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de Santa Clara (Poble Nou)", + "lat": "41.38636", + "lon": "2.01183", + "id": 52370 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "l'Aldea", + "lat": "40.74451", + "lon": "0.61882", + "id": 4642519995, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Raval de Clara", + "lat": "40.75054", + "lon": "0.62440", + "id": 10707 + } + ], + "matchStreet": 1 + }, + { + "name": "Reus", + "names": { + "ru": "Реус", + "uk": "Реус", + "el": "Ρέους", + "lt": "Reusas" + }, + "lat": "41.15555", + "lon": "1.10762", + "id": 152377926, + "type": "CITY", + "listOfStreets": [ + { + "name": "Carrer de Santa Clara", + "lat": "41.15378", + "lon": "1.11140", + "id": 88423 + }, + { + "name": "Carrer de l'advocada Clara Campoamor", + "lat": "41.14903", + "lon": "1.09717", + "id": 34460 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Torredembarra", + "lat": "41.14615", + "lon": "1.39595", + "id": 1460478559, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Avinguda de Clarà", + "lat": "41.15148", + "lon": "1.41867", + "id": 90162 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Badalona", + "names": { + "kk": "Бадалона", + "be": "Бадалона", + "ru": "Бадалона", + "ko": "바달로나", + "bg": "Бадалона", + "el": "Μπαδαλόνα", + "sa": "बाडालोना", + "zh": "巴达洛纳", + "ar": "بادالونا", + "hy": "Բադալոնա", + "th": "บาดาโลนา", + "ka": "ბადალონა", + "uk": "Бадалона", + "ja": "バダロナ", + "fa": "بادالونا", + "sr": "Бадалона" + }, + "lat": "41.44935", + "lon": "2.24825", + "id": 52228684, + "type": "CITY", + "listOfStreets": [ + { + "name": "carrer de l'Escultor Clarà", + "lat": "41.44884", + "lon": "2.22741", + "id": 28836, + "intersectedStreets": [ + { + "name": "carrer de l'Estrella", + "lat": "41.44869", + "lon": "2.22550" + }, + { + "name": "carrer del General Moragues", + "lat": "41.44869", + "lon": "2.22550" + }, + { + "name": "carrer del Cid Campeador", + "lat": "41.44852", + "lon": "2.22857" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Garraf", + "lat": "41.25274", + "lon": "1.90248", + "id": 5025041121, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Passatge de Mossèn Fèlix Clarà", + "lat": "41.23587", + "lon": "1.80989", + "id": 3638, + "buildings": [], + "intersectedStreets": [ + { + "name": "Carrer d'en Pau Barrabeig", + "lat": "41.23567", + "lon": "1.80995" + }, + { + "name": "Carrer de la Carreta", + "lat": "41.23562", + "lon": "1.81013" + }, + { + "name": "Carrer Major (Sitges)", + "lat": "41.23633", + "lon": "1.81004" + }, + { + "name": "Carrer de l'Hort de Can Falç", + "lat": "41.23612", + "lon": "1.80993" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Terrassa", + "names": { + "ar": "تاراسا", + "ru": "Террасса", + "el": "Τεράσα", + "lt": "Terasa", + "ca": "Terrassa", + "es": "Tarrasa", + "sr": "Тараса" + }, + "lat": "41.56973", + "lon": "2.01320", + "id": 288139569, + "type": "CITY", + "listOfStreets": [ + { + "name": "Carrer de l'Escultor Clarà (La Maurina)", + "lat": "41.56165", + "lon": "1.99511", + "id": 23480, + "intersectedStreets": [ + { + "name": "Carrer de Felip II (La Maurina)", + "lat": "41.56139", + "lon": "1.99488" + }, + { + "name": "Carrer d'Orà", + "lat": "41.56139", + "lon": "1.99488" + }, + { + "name": "Carrer de Gandia", + "lat": "41.56279", + "lon": "1.99524" + } + ] + }, + { + "name": "Carrer de Clara Campoamor (Can Roca)", + "lat": "41.58287", + "lon": "2.01108", + "id": 15310, + "intersectedStreets": [ + { + "name": "Carrer de Sabadell (Pla del Bon Aire)", + "lat": "41.58263", + "lon": "2.01157" + }, + { + "name": "Carrer de l'Estatut (Can Roca)", + "lat": "41.58263", + "lon": "2.01157" + }, + { + "name": "Passatge de Mercè Rodoreda", + "lat": "41.58287", + "lon": "2.01108" + }, + { + "name": "Carrer de Ferran Casablancas (Can Roca)", + "lat": "41.58301", + "lon": "2.01078" + }, + { + "name": "Carrer d'Anna Frank (Can Roca)", + "lat": "41.58340", + "lon": "2.00998" + } + ] + }, + { + "name": "Carrer de la poetessa Clarà", + "lat": "41.57231", + "lon": "2.02453", + "id": 23007, + "intersectedStreets": [ + { + "name": "Avinguda de Jaume I (Ègara)", + "lat": "41.57150", + "lon": "2.02256" + }, + { + "name": "Carrer de Prat de la Riba (Sant Pere Nord)", + "lat": "41.57150", + "lon": "2.02256" + }, + { + "name": "Carrer de Girona (Sant Pere Nord)", + "lat": "41.57223", + "lon": "2.02352" + }, + { + "name": "Carrer de Josep Maria Palau (Sant Pere Nord)", + "lat": "41.57224", + "lon": "2.02376" + }, + { + "name": "Carrer de Pompeu Fabra (Can Tusell)", + "lat": "41.57234", + "lon": "2.02472" + }, + { + "name": "Carrer del pintor Huguet", + "lat": "41.57234", + "lon": "2.02472" + }, + { + "name": "Carrer de Miguel Palomares", + "lat": "41.57237", + "lon": "2.02511" + }, + { + "name": "Carrer del Doctor Ferran (Sant Pere Nord)", + "lat": "41.57174", + "lon": "2.02271" + }, + { + "name": "Carrer de Provença (Can Tusell)", + "lat": "41.57250", + "lon": "2.02552" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Poble Nou", + "lat": "41.38736", + "lon": "2.01179", + "id": 3179930069, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Carrer de Santa Clara", + "lat": "41.38636", + "lon": "2.01185", + "id": 52371 + } + ], + "matchStreet": 1 + }, + { + "name": "Sant Pol de Mar", + "names": { + "ca": "Sant Pol de Mar", + "es": "San Pol de Mar" + }, + "lat": "41.60302", + "lon": "2.62380", + "id": 571251818, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Santa Clara", + "lat": "41.60197", + "lon": "2.62189", + "id": 10520 + }, + { + "name": "Carrer Santa Clara", + "lat": "41.60294", + "lon": "2.62316", + "id": 43715 + }, + { + "name": "Carrer de Santa Clara", + "lat": "41.60221", + "lon": "2.62241", + "id": 43725 + } + ], + "matchStreet": 1 + }, + { + "name": "el Catllar", + "lat": "41.17479", + "lon": "1.32559", + "id": 886148666, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Camí del Mas de Clarà", + "lat": "41.15447", + "lon": "1.31885", + "id": 80732 + } + ], + "matchStreet": 1 + }, + { + "name": "Lleida", + "names": { + "ar": "لاردة", + "ru": "Ллейда", + "el": "Λιέιδα", + "lt": "Lerida", + "fr": "Lérida", + "ca": "Lleida", + "es": "Lérida", + "sr": "Љеида" + }, + "lat": "41.61476", + "lon": "0.62678", + "id": 124083658, + "type": "CITY", + "listOfStreets": [ + { + "name": "Plaça de Clara Campoamor", + "lat": "41.62731", + "lon": "0.63285", + "id": 102127 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Gavà", + "lat": "41.30509", + "lon": "2.00631", + "id": 1470838298, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de Clara Campoamor", + "lat": "41.30860", + "lon": "1.98805", + "id": 14336, + "intersectedStreets": [ + { + "name": "Carrer de Montserrat Roig", + "lat": "41.30899", + "lon": "1.98657" + }, + { + "name": "Rambla Pompeu Fabra", + "names": { + "ca": "rambla Pompeu Fabra" + }, + "lat": "41.30868", + "lon": "1.99097" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Moià", + "lat": "41.81304", + "lon": "2.09783", + "id": 269726056, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrer del Castell de Clarà", + "lat": "41.81331", + "lon": "2.08854", + "id": 34334, + "intersectedStreets": [ + { + "name": "Carrer de Jacint Icart", + "lat": "41.81375", + "lon": "2.08770" + }, + { + "name": "Carrer de Sant Andreu", + "lat": "41.81278", + "lon": "2.08929" + }, + { + "name": "Carrer de Sant Pere", + "lat": "41.81278", + "lon": "2.08929" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Cabrera de Mar", + "lat": "41.52770", + "lon": "2.39251", + "id": 523051419, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "carrer de Clarà", + "lat": "41.52795", + "lon": "2.39953", + "id": 68176, + "intersectedStreets": [ + { + "name": "carrer de Sant Sebastià", + "lat": "41.52839", + "lon": "2.40019" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Vilafant", + "lat": "42.25153", + "lon": "2.94180", + "id": 1460478326, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrer de les Closes d'en Clarà", + "lat": "42.25254", + "lon": "2.95376", + "id": 37851, + "intersectedStreets": [ + { + "name": "Carretera de les Forques", + "lat": "42.25247", + "lon": "2.95380" + }, + { + "name": "Avinguda de Maria Torres", + "lat": "42.25285", + "lon": "2.95511" + }, + { + "name": "Cami del Puig Martí", + "lat": "42.25285", + "lon": "2.95511" + }, + { + "name": "Carrer de la Muga", + "lat": "42.25265", + "lon": "2.95412" + }, + { + "name": "Avinguda de les Mèlies", + "lat": "42.25239", + "lon": "2.95077" + }, + { + "name": "Carrer Anyet", + "lat": "42.25243", + "lon": "2.95092" + }, + { + "name": "Carrer Llierca", + "lat": "42.25257", + "lon": "2.95172" + }, + { + "name": "Carrer Arnera", + "lat": "42.25257", + "lon": "2.95255" + }, + { + "name": "Carrer del Fluvià", + "lat": "42.25257", + "lon": "2.95339" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Manresa", + "names": { + "ru": "Манреса", + "ja": "マンレザ" + }, + "lat": "41.72890", + "lon": "1.82867", + "id": 1470838508, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer Vell de Santa Clara", + "lat": "41.72146", + "lon": "1.83283", + "id": 92484 + }, + { + "name": "Carrer Nou de Santa Clara (Les Escodines)", + "lat": "41.72235", + "lon": "1.83070", + "id": 74567 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "les Peces", + "lat": "41.25500", + "lon": "1.50148", + "id": 388127073, + "type": "HAMLET", + "listOfStreets": [ + { + "name": "Carrer Josep Clarà", + "lat": "41.25371", + "lon": "1.50272", + "id": 73976 + } + ], + "matchStreet": 1 + }, + { + "name": "Vic", + "enName": "Vic", + "names": { + "ca": "Vic", + "es": "Vic", + "zh": "比克" + }, + "lat": "41.93020", + "lon": "2.25460", + "id": 1470839536, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer del Molí d'en Clarà", + "lat": "41.91336", + "lon": "2.22297", + "id": 64436, + "intersectedStreets": [ + { + "name": "Carrer del 2 de Novembre de 1930", + "lat": "41.91294", + "lon": "2.22321" + }, + { + "name": "Carrer d'Antoni Vila i Benet", + "lat": "41.91336", + "lon": "2.22297" + }, + { + "name": "Carrer de l'Ermita de Sant Sebastià", + "lat": "41.91379", + "lon": "2.22269" + } + ] + }, + { + "name": "Plaça de Santa Clara", + "lat": "41.93107", + "lon": "2.25546", + "id": 27214 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Sant Llorenç Savall", + "lat": "41.67947", + "lon": "2.05796", + "id": 540818296, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrer Josep Clara", + "lat": "41.68389", + "lon": "2.05298", + "id": 41474, + "intersectedStreets": [ + { + "name": "Avinguda de Sant Llorenç", + "lat": "41.68294", + "lon": "2.05296" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Barcelona", + "names": { + "de": "Barcelona", + "hi": "बार्सॆलोना", + "be": "Барселона", + "kn": "ಬಾರ್ಸೆಲೋನಾ", + "ru": "Барселона", + "pt": "Barcelona", + "el": "Βαρκελώνη", + "lt": "Barselona", + "it": "Barcellona", + "fr": "Barcelone", + "es": "Barcelona", + "zh": "巴塞罗那", + "eu": "Bartzelona", + "ar": "برشلونة", + "oc": "Barcelona", + "uk": "Барселона", + "ja": "バルセロナ", + "ckb": "بارسێلۆنا", + "ca": "Barcelona", + "sr": "Барселона" + }, + "lat": "41.38289", + "lon": "2.17744", + "id": 152364165, + "type": "CITY", + "listOfStreets": [ + { + "name": "Carrer de Clarà Ayats", + "lat": "41.43085", + "lon": "2.14128", + "id": 73839, + "buildings": [], + "intersectedStreets": [ + { + "name": "Carrer de Roig i Solé", + "lat": "41.43087", + "lon": "2.14094" + }, + { + "name": "Carrer de la Pintura", + "lat": "41.43085", + "lon": "2.14128" + }, + { + "name": "Carrer de la Música (Montbau)", + "lat": "41.43176", + "lon": "2.14167" + }, + { + "name": "Carrer de la Poesia (Montbau)", + "lat": "41.43254", + "lon": "2.14203" + } + ] + }, + { + "name": "Passatge de Santa Clara", + "lat": "41.38386", + "lon": "2.17744", + "id": 29833 + }, + { + "name": "Carrer del Comte de Santa Clara", + "lat": "41.37831", + "lon": "2.18930", + "id": 13100 + }, + { + "name": "Baixada de Santa Clara", + "lat": "41.38381", + "lon": "2.17731", + "id": 2733 + }, + { + "name": "Carrer de Clara Zetkin", + "lat": "41.42394", + "lon": "2.19173", + "id": 95176, + "intersectedStreets": [ + { + "name": "Carrer Gran de la Sagrera", + "names": { + "alt_name": "Carrer de la Sagrera" + }, + "lat": "41.42394", + "lon": "2.19109" + }, + { + "name": "Carrer de Martí Molins", + "lat": "41.42394", + "lon": "2.19109" + }, + { + "name": "Carrer de Berenguer de Palou (la Sagrera)", + "lat": "41.42394", + "lon": "2.19173" + }, + { + "name": "Carrer del Pont del Treball Digne (Sant Martí de Provençals)", + "lat": "41.42394", + "lon": "2.19173" + } + ] + }, + { + "name": "Carrer de Clarà", + "lat": "41.44683", + "lon": "2.19926", + "id": 27484, + "intersectedStreets": [ + { + "name": "Carrer de Campins (Baró de Viver)", + "lat": "41.44720", + "lon": "2.19903" + }, + { + "name": "Carrer de Clariana (Baró de Viver)", + "lat": "41.44683", + "lon": "2.19926" + }, + { + "name": "Carrer de Quito", + "lat": "41.44617", + "lon": "2.19965" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Vilafranca del Penedès", + "names": { + "ca": "Vilafranca del Penedès", + "es": "Villafranca del Panadés" + }, + "lat": "41.34639", + "lon": "1.69951", + "id": 1470839547, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de Santa Clara", + "lat": "41.34637", + "lon": "1.70142", + "id": 94174 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Alella", + "names": { + "ru": "Алелья", + "ca": "Alella", + "es": "Alella" + }, + "lat": "41.49529", + "lon": "2.29428", + "id": 523051389, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "riera Coma Clara", + "lat": "41.50716", + "lon": "2.29975", + "id": 67640, + "intersectedStreets": [ + { + "name": "avinguda de Sant Mateu", + "lat": "41.50422", + "lon": "2.29730" + }, + { + "name": "Carretera de Granollers al Masnou", + "lat": "41.50315", + "lon": "2.29524" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "el Prat de Llobregat", + "names": { + "ru": "Эль-Прат-де-Льобрегат", + "ca": "el Prat de Llobregat", + "es": "El Prat de Llobregat" + }, + "lat": "41.32464", + "lon": "2.09526", + "id": 1470838266, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Passatge de Clara Campoamor", + "lat": "41.31949", + "lon": "2.09474", + "id": 95379, + "intersectedStreets": [ + { + "name": "Avinguda de l'Onze de Setembre", + "lat": "41.31956", + "lon": "2.09470" + }, + { + "name": "Carrer de Jaume Codina (les Cases de La Seda)", + "lat": "41.31895", + "lon": "2.09502" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Argentona", + "lat": "41.55543", + "lon": "2.40028", + "id": 1470838014, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de la Riera de Clarà", + "lat": "41.56730", + "lon": "2.39326", + "id": 30303 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Arenys de Mar", + "lat": "41.57971", + "lon": "2.54915", + "id": 1470838013, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer Sta. Clara", + "lat": "41.58398", + "lon": "2.54647", + "id": 47937, + "intersectedStreets": [ + { + "name": "Riera de Pare Fita", + "lat": "41.58406", + "lon": "2.54698" + }, + { + "name": "Pujada de Lourdes", + "names": { + "alt_name": "Pujada de Lourdes" + }, + "lat": "41.58487", + "lon": "2.54516" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "el Vendrell", + "names": { + "ca": "el Vendrell" + }, + "lat": "41.21997", + "lon": "1.53487", + "id": 1470838269, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer Josep Clarà", + "lat": "41.18406", + "lon": "1.53461", + "id": 42024 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "la Sagrera", + "lat": "41.42375", + "lon": "2.18958", + "id": 2875927736, + "type": "SUBURB", + "listOfStreets": [ + { + "name": "Carrer de Clara Zetkin", + "lat": "41.42395", + "lon": "2.19173", + "id": 95177, + "intersectedStreets": [ + { + "name": "Carrer Gran de la Sagrera", + "names": { + "alt_name": "Carrer de la Sagrera" + }, + "lat": "41.42397", + "lon": "2.19109" + }, + { + "name": "Carrer de Martí Molins", + "lat": "41.42397", + "lon": "2.19109" + }, + { + "name": "Carrer de Berenguer de Palou", + "lat": "41.42397", + "lon": "2.19173" + }, + { + "name": "Carrer del Pont del Treball Digne", + "lat": "41.42397", + "lon": "2.19173" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Castelló d'Empuries", + "lat": "42.25973", + "lon": "3.07411", + "id": 1949049715, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Carrer de Santa Clara (Castelló d'Empúries)", + "lat": "42.25630", + "lon": "3.07744", + "id": 1505 + }, + { + "name": "Carrer Santa Clara (Castelló d'Empúries)", + "lat": "42.25503", + "lon": "3.07317", + "id": 17837 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Olot", + "lat": "42.18222", + "lon": "2.48903", + "id": 1470838619, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Plaça de Clarà", + "lat": "42.18109", + "lon": "2.48503", + "id": 115438, + "intersectedStreets": [ + { + "name": "Carrer del Pare Roca", + "lat": "42.18109", + "lon": "2.48503" + }, + { + "name": "Carrer del Pintor Galwey", + "lat": "42.18109", + "lon": "2.48503" + }, + { + "name": "Avinguda dels Reis Catòlics (Malagrida)", + "lat": "42.18023", + "lon": "2.48428" + }, + { + "name": "Carrer de la Verge de Fàtima (Desemparats)", + "lat": "42.18023", + "lon": "2.48428" + }, + { + "name": "Carrer de l'Escultor Josep Clarà", + "lat": "42.18023", + "lon": "2.48428" + } + ] + }, + { + "name": "Carrer de l'Escultor Josep Clarà", + "lat": "42.18010", + "lon": "2.48454", + "id": 115439, + "intersectedStreets": [ + { + "name": "Avinguda dels Reis Catòlics (Malagrida)", + "lat": "42.18023", + "lon": "2.48428" + }, + { + "name": "Carrer de la Verge de Fàtima (Desemparats)", + "lat": "42.18023", + "lon": "2.48428" + }, + { + "name": "Plaça de Clarà", + "lat": "42.18023", + "lon": "2.48428" + }, + { + "name": "Eix Diagonal (Malagrida)", + "names": { + "es": "eje diagonal (Malagrida)" + }, + "lat": "42.17985", + "lon": "2.48510" + }, + { + "name": "Carrer del Bisbe Vilanova", + "lat": "42.17985", + "lon": "2.48510" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Casserres", + "names": { + "es": "Casserres" + }, + "lat": "42.01382", + "lon": "1.84304", + "id": 303713456, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrer Riera de Clarà", + "lat": "42.01364", + "lon": "1.83839", + "id": 27572, + "intersectedStreets": [ + { + "name": "carrer Bisbe Comelles", + "lat": "42.01329", + "lon": "1.83843" + }, + { + "name": "Carrer de Sant Rafel", + "lat": "42.01364", + "lon": "1.83839" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Malgrat de Mar", + "lat": "41.64592", + "lon": "2.74141", + "id": 1460478645, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrer de l'Escultor Clarà", + "lat": "41.64515", + "lon": "2.73362", + "id": 85646, + "intersectedStreets": [ + { + "name": "Avinguda dels Països Catalans", + "lat": "41.64418", + "lon": "2.73021" + }, + { + "name": "Carrer d'Enric Borràs", + "lat": "41.64446", + "lon": "2.73111" + }, + { + "name": "Carrer de Joan Esquena i Torró", + "lat": "41.64455", + "lon": "2.73150" + }, + { + "name": "Carrer de Manuel de Falla", + "lat": "41.64487", + "lon": "2.73266" + }, + { + "name": "Avinguda de la Costa Brava", + "lat": "41.64523", + "lon": "2.73508" + }, + { + "name": "Avinguda de la Verge de Montserrat", + "lat": "41.64523", + "lon": "2.73508" + } + ] + } + ], + "matchStreet": 1 + }, + { + "name": "Berga", + "lat": "42.10114", + "lon": "1.84547", + "id": 1470838103, + "type": "TOWN", + "listOfStreets": [ + { + "name": "Pont de Clarà", + "lat": "42.06680", + "lon": "1.79620", + "id": 103909 + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Sant Cugat del Vallès", + "names": { + "es": "San Cugat del Vallés" + }, + "lat": "41.47285", + "lon": "2.08178", + "id": 1470838828, + "type": "TOWN", + "listOfStreets": [ + { + "name": "carrer de Josep Clarà", + "lat": "41.47341", + "lon": "2.07253", + "id": 47376, + "intersectedStreets": [ + { + "name": "Carrer de Domènec Oristrell", + "lat": "41.47304", + "lon": "2.07208" + }, + { + "name": "Carrer de Manel Farrés", + "lat": "41.47341", + "lon": "2.07253" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Barri Gòtic", + "enName": "Gothic Quarter", + "names": { + "de": "Gotisches Viertel", + "ru": "Готический квартал", + "uk": "Готичний квартал", + "he": "הרובע הגותי", + "es": "Barrio Gótico" + }, + "lat": "41.38339", + "lon": "2.17690", + "id": 1433947776, + "type": "SUBURB", + "listOfStreets": [ + { + "name": "Passatge de Santa Clara", + "lat": "41.38384", + "lon": "2.17742", + "id": 29834 + }, + { + "name": "Baixada de Santa Clara", + "lat": "41.38381", + "lon": "2.17729", + "id": 2734 + } + ], + "matchStreet": 1 + }, + { + "name": "Sant Adrià de Besòs", + "names": { + "ca": "Sant Adrià de Besòs", + "es": "San Adrián de Besós" + }, + "lat": "41.43048", + "lon": "2.21827", + "id": 1470838823, + "type": "TOWN", + "listOfStreets": [ + { + "name": "carrer de Clara Campoamor", + "lat": "41.42384", + "lon": "2.21958", + "id": 37759, + "intersectedStreets": [ + { + "name": "Ronda del Barcelonés", + "lat": "41.42344", + "lon": "2.21900" + }, + { + "name": "Carrer de Vicenç Ferrer (la Catalana)", + "lat": "41.42384", + "lon": "2.21958" + }, + { + "name": "Avinguda de la Catalana", + "lat": "41.42453", + "lon": "2.22050" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Masquefa", + "lat": "41.50263", + "lon": "1.81107", + "id": 1460478685, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrer de Santa Clara", + "lat": "41.49762", + "lon": "1.81457", + "id": 48462 + } + ], + "matchStreet": 1 + }, + { + "name": "Albinyana", + "lat": "41.24547", + "lon": "1.48674", + "id": 395228508, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrer Josep Clarà (les Peces)", + "lat": "41.25373", + "lon": "1.50272", + "id": 73975 + } + ], + "matchStreet": 1 + }, + { + "name": "l'Hospitalet de Llobregat", + "names": { + "ru": "Оспиталет-де-Льобрегат", + "el": "Οσπιταλέτ ντε Λιοβρεγάτ", + "ca": "l'Hospitalet de Llobregat", + "es": "Hospitalet de Llobregat", + "sr": "Лоспиталет де Љобрегат" + }, + "lat": "41.35986", + "lon": "2.09978", + "id": 939880925, + "type": "CITY", + "listOfStreets": [ + { + "name": "Carrer de Clara Campoamor (la Torrassa)", + "lat": "41.36687", + "lon": "2.12330", + "id": 92544, + "intersectedStreets": [ + { + "name": "Carrer de Santa Eulàlia", + "lat": "41.36657", + "lon": "2.12341" + }, + { + "name": "Carrer d'Àngel Guimerà (Santa Eulàlia)", + "lat": "41.36657", + "lon": "2.12341" + }, + { + "name": "Passatge de Salvadors", + "lat": "41.36752", + "lon": "2.12304" + } + ] + }, + { + "name": "Carrer de Clara Campoamor (el Gornal)", + "lat": "41.35671", + "lon": "2.11513", + "id": 36260, + "intersectedStreets": [ + { + "name": "Travessia Industrial", + "lat": "41.35819", + "lon": "2.11397" + }, + { + "name": "Carrer de Colom (el Gornal)", + "lat": "41.35768", + "lon": "2.11437" + }, + { + "name": "Carrer de Caterina Albert (el Gornal)", + "lat": "41.35671", + "lon": "2.11513" + }, + { + "name": "Avinguda d'Amèrica", + "lat": "41.35666", + "lon": "2.11517" + } + ] + } + ], + "init": 1, + "matchStreet": 1 + }, + { + "name": "Castelló d'Empúries", + "names": { + "es": "Castellón de Ampurias" + }, + "lat": "42.25910", + "lon": "3.07465", + "id": 130118172, + "type": "VILLAGE", + "listOfStreets": [ + { + "name": "Carrer de Santa Clara", + "lat": "42.25630", + "lon": "3.07744", + "id": 1506 + }, + { + "name": "Carrer Santa Clara", + "lat": "42.25503", + "lon": "3.07317", + "id": 17838 + } + ], + "matchStreet": 1 + }, + { + "name": "Centre Històric", + "lat": "41.72340", + "lon": "1.82701", + "id": 4356132667, + "type": "SUBURB", + "listOfStreets": [ + { + "name": "Carrer Nou de Santa Clara", + "lat": "41.72235", + "lon": "1.83073", + "id": 74569 + } + ], + "matchStreet": 1 + }, + { + "name": "La Maurina", + "lat": "41.56081", + "lon": "1.99582", + "id": 3427894669, + "type": "SUBURB", + "listOfStreets": [ + { + "name": "Carrer de l'Escultor Clarà", + "lat": "41.56163", + "lon": "1.99511", + "id": 23481, + "intersectedStreets": [ + { + "name": "Carrer de Felip II", + "lat": "41.56137", + "lon": "1.99488" + }, + { + "name": "Carrer d'Orà", + "lat": "41.56137", + "lon": "1.99488" + }, + { + "name": "Carrer de Gandia", + "lat": "41.56277", + "lon": "1.99524" + } + ] + } + ], + "matchStreet": 1 + } + ] +} \ No newline at end of file diff --git a/OsmAnd-telegram/build.gradle b/OsmAnd-telegram/build.gradle index 57141c4238..aa13df28b0 100644 --- a/OsmAnd-telegram/build.gradle +++ b/OsmAnd-telegram/build.gradle @@ -135,7 +135,9 @@ dependencies { implementation project(path: ':OsmAnd-java', configuration: 'android') implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation( "org.jetbrains.kotlin:kotlin-stdlib:1.2.71") { + exclude group: 'org.jetbrains', module: 'annotations' + } implementation 'com.android.support:appcompat-v7:28.0.0-rc01' implementation 'com.android.support:design:28.0.0-rc01' implementation 'com.android.support:customtabs:28.0.0-rc01' @@ -146,4 +148,5 @@ dependencies { implementation("com.github.HITGIF:TextFieldBoxes:1.4.4") { exclude group: 'com.android.support' } + implementation 'org.jetbrains:annotations-java5:15.0' } diff --git a/OsmAnd-telegram/res/drawable-hdpi/ic_action_altitude_range.png b/OsmAnd-telegram/res/drawable-hdpi/ic_action_altitude_range.png new file mode 100644 index 0000000000..d3c1ff597b Binary files /dev/null and b/OsmAnd-telegram/res/drawable-hdpi/ic_action_altitude_range.png differ diff --git a/OsmAnd-telegram/res/drawable-hdpi/ic_action_date_add.png b/OsmAnd-telegram/res/drawable-hdpi/ic_action_date_add.png new file mode 100644 index 0000000000..f7e9962a63 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-hdpi/ic_action_date_add.png differ diff --git a/OsmAnd-telegram/res/drawable-hdpi/ic_action_date_end.png b/OsmAnd-telegram/res/drawable-hdpi/ic_action_date_end.png new file mode 100644 index 0000000000..0af2e77948 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-hdpi/ic_action_date_end.png differ diff --git a/OsmAnd-telegram/res/drawable-hdpi/ic_action_date_start.png b/OsmAnd-telegram/res/drawable-hdpi/ic_action_date_start.png new file mode 100644 index 0000000000..f27db2f105 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-hdpi/ic_action_date_start.png differ diff --git a/OsmAnd-telegram/res/drawable-hdpi/ic_action_distance_16dp.png b/OsmAnd-telegram/res/drawable-hdpi/ic_action_distance_16dp.png new file mode 100644 index 0000000000..bebd1402c4 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-hdpi/ic_action_distance_16dp.png differ diff --git a/OsmAnd-telegram/res/drawable-hdpi/ic_action_share.png b/OsmAnd-telegram/res/drawable-hdpi/ic_action_share.png new file mode 100644 index 0000000000..b5219f938a Binary files /dev/null and b/OsmAnd-telegram/res/drawable-hdpi/ic_action_share.png differ diff --git a/OsmAnd-telegram/res/drawable-hdpi/ic_action_speed_average.png b/OsmAnd-telegram/res/drawable-hdpi/ic_action_speed_average.png new file mode 100644 index 0000000000..d514a54ffb Binary files /dev/null and b/OsmAnd-telegram/res/drawable-hdpi/ic_action_speed_average.png differ diff --git a/OsmAnd-telegram/res/drawable-hdpi/ic_action_timeline.png b/OsmAnd-telegram/res/drawable-hdpi/ic_action_timeline.png new file mode 100644 index 0000000000..5cc366eec5 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-hdpi/ic_action_timeline.png differ diff --git a/OsmAnd-telegram/res/drawable-mdpi/ic_action_altitude_range.png b/OsmAnd-telegram/res/drawable-mdpi/ic_action_altitude_range.png new file mode 100644 index 0000000000..82a122006f Binary files /dev/null and b/OsmAnd-telegram/res/drawable-mdpi/ic_action_altitude_range.png differ diff --git a/OsmAnd-telegram/res/drawable-mdpi/ic_action_date_add.png b/OsmAnd-telegram/res/drawable-mdpi/ic_action_date_add.png new file mode 100644 index 0000000000..ee38eab5bd Binary files /dev/null and b/OsmAnd-telegram/res/drawable-mdpi/ic_action_date_add.png differ diff --git a/OsmAnd-telegram/res/drawable-mdpi/ic_action_date_end.png b/OsmAnd-telegram/res/drawable-mdpi/ic_action_date_end.png new file mode 100644 index 0000000000..f342cde0cd Binary files /dev/null and b/OsmAnd-telegram/res/drawable-mdpi/ic_action_date_end.png differ diff --git a/OsmAnd-telegram/res/drawable-mdpi/ic_action_date_start.png b/OsmAnd-telegram/res/drawable-mdpi/ic_action_date_start.png new file mode 100644 index 0000000000..c15212f4c0 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-mdpi/ic_action_date_start.png differ diff --git a/OsmAnd-telegram/res/drawable-mdpi/ic_action_distance_16dp.png b/OsmAnd-telegram/res/drawable-mdpi/ic_action_distance_16dp.png new file mode 100644 index 0000000000..1c91ed322b Binary files /dev/null and b/OsmAnd-telegram/res/drawable-mdpi/ic_action_distance_16dp.png differ diff --git a/OsmAnd-telegram/res/drawable-mdpi/ic_action_share.png b/OsmAnd-telegram/res/drawable-mdpi/ic_action_share.png new file mode 100644 index 0000000000..9acdb7b3b6 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-mdpi/ic_action_share.png differ diff --git a/OsmAnd-telegram/res/drawable-mdpi/ic_action_speed_average.png b/OsmAnd-telegram/res/drawable-mdpi/ic_action_speed_average.png new file mode 100644 index 0000000000..55220303a2 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-mdpi/ic_action_speed_average.png differ diff --git a/OsmAnd-telegram/res/drawable-mdpi/ic_action_timeline.png b/OsmAnd-telegram/res/drawable-mdpi/ic_action_timeline.png new file mode 100644 index 0000000000..a5ef5bd4fe Binary files /dev/null and b/OsmAnd-telegram/res/drawable-mdpi/ic_action_timeline.png differ diff --git a/OsmAnd-telegram/res/drawable-xhdpi/ic_action_altitude_range.png b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_altitude_range.png new file mode 100644 index 0000000000..b96be7510f Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_altitude_range.png differ diff --git a/OsmAnd-telegram/res/drawable-xhdpi/ic_action_date_add.png b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_date_add.png new file mode 100644 index 0000000000..61f43922ce Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_date_add.png differ diff --git a/OsmAnd-telegram/res/drawable-xhdpi/ic_action_date_end.png b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_date_end.png new file mode 100644 index 0000000000..4de11b71f3 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_date_end.png differ diff --git a/OsmAnd-telegram/res/drawable-xhdpi/ic_action_date_start.png b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_date_start.png new file mode 100644 index 0000000000..c2032d5c18 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_date_start.png differ diff --git a/OsmAnd-telegram/res/drawable-xhdpi/ic_action_distance_16dp.png b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_distance_16dp.png new file mode 100644 index 0000000000..578af8e5eb Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_distance_16dp.png differ diff --git a/OsmAnd-telegram/res/drawable-xhdpi/ic_action_share.png b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_share.png new file mode 100644 index 0000000000..ddcde8ddfb Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_share.png differ diff --git a/OsmAnd-telegram/res/drawable-xhdpi/ic_action_speed_average.png b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_speed_average.png new file mode 100644 index 0000000000..9de62628f5 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_speed_average.png differ diff --git a/OsmAnd-telegram/res/drawable-xhdpi/ic_action_timeline.png b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_timeline.png new file mode 100644 index 0000000000..2eb0d10d2a Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xhdpi/ic_action_timeline.png differ diff --git a/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_altitude_range.png b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_altitude_range.png new file mode 100644 index 0000000000..073ca37a2d Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_altitude_range.png differ diff --git a/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_date_add.png b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_date_add.png new file mode 100644 index 0000000000..e46425373d Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_date_add.png differ diff --git a/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_date_end.png b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_date_end.png new file mode 100644 index 0000000000..ae6ea10cf3 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_date_end.png differ diff --git a/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_date_start.png b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_date_start.png new file mode 100644 index 0000000000..11748b8ace Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_date_start.png differ diff --git a/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_distance_16dp.png b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_distance_16dp.png new file mode 100644 index 0000000000..817e0f9559 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_distance_16dp.png differ diff --git a/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_share.png b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_share.png new file mode 100644 index 0000000000..191e70da0e Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_share.png differ diff --git a/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_speed_average.png b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_speed_average.png new file mode 100644 index 0000000000..82d5b48cf9 Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_speed_average.png differ diff --git a/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_timeline.png b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_timeline.png new file mode 100644 index 0000000000..452d6879bc Binary files /dev/null and b/OsmAnd-telegram/res/drawable-xxhdpi/ic_action_timeline.png differ diff --git a/OsmAnd-telegram/res/drawable/btn_round_border.xml b/OsmAnd-telegram/res/drawable/btn_round_border.xml new file mode 100644 index 0000000000..ae932320c8 --- /dev/null +++ b/OsmAnd-telegram/res/drawable/btn_round_border.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd-telegram/res/layout/bottom_sheet_add_new_device.xml b/OsmAnd-telegram/res/layout/bottom_sheet_add_new_device.xml new file mode 100644 index 0000000000..f719ee2af8 --- /dev/null +++ b/OsmAnd-telegram/res/layout/bottom_sheet_add_new_device.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml b/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml index b196c0372b..5d2bacf752 100644 --- a/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml +++ b/OsmAnd-telegram/res/layout/fragement_settings_dialog.xml @@ -95,11 +95,13 @@ app:typeface="@string/font_roboto_medium" /> + + + + + + + + diff --git a/OsmAnd-telegram/res/layout/fragment_live_now_tab.xml b/OsmAnd-telegram/res/layout/fragment_live_now_tab.xml index 9dc5cd5b08..b23ed65e33 100644 --- a/OsmAnd-telegram/res/layout/fragment_live_now_tab.xml +++ b/OsmAnd-telegram/res/layout/fragment_live_now_tab.xml @@ -117,13 +117,32 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + + + android:layout_height="match_parent"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd-telegram/res/layout/fragment_user_gpx_info.xml b/OsmAnd-telegram/res/layout/fragment_user_gpx_info.xml new file mode 100644 index 0000000000..4d7636dc1b --- /dev/null +++ b/OsmAnd-telegram/res/layout/fragment_user_gpx_info.xml @@ -0,0 +1,526 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd-telegram/res/layout/live_now_chat_card.xml b/OsmAnd-telegram/res/layout/live_now_chat_card.xml index f8609ce72b..a72f3a80fe 100644 --- a/OsmAnd-telegram/res/layout/live_now_chat_card.xml +++ b/OsmAnd-telegram/res/layout/live_now_chat_card.xml @@ -40,7 +40,8 @@ android:layout_marginEnd="@dimen/content_padding_standard" android:layout_marginRight="@dimen/content_padding_standard" android:layout_weight="1" - android:orientation="vertical"> + android:orientation="vertical" + android:paddingBottom="@dimen/content_padding_half"> + android:gravity="center_vertical"> - - - + android:orientation="horizontal" + android:visibility="gone" + tools:visibility="visible"> + + + + + + + + + + + + + + + android:layout_height="wrap_content"> + + diff --git a/OsmAnd-telegram/res/layout/my_location_sharing_chat.xml b/OsmAnd-telegram/res/layout/my_location_sharing_chat.xml index 08d8a25c1d..baf007cc7c 100644 --- a/OsmAnd-telegram/res/layout/my_location_sharing_chat.xml +++ b/OsmAnd-telegram/res/layout/my_location_sharing_chat.xml @@ -171,6 +171,44 @@ + + + + + + + + + + + + diff --git a/OsmAnd-telegram/res/menu/bottom_navigation_menu.xml b/OsmAnd-telegram/res/menu/bottom_navigation_menu.xml index ba71c44bc8..d12e0981d5 100644 --- a/OsmAnd-telegram/res/menu/bottom_navigation_menu.xml +++ b/OsmAnd-telegram/res/menu/bottom_navigation_menu.xml @@ -9,4 +9,9 @@ android:id="@+id/action_live_now" android:icon="@drawable/ic_action_live_now" android:title="@string/live_now"/> + + \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-ast/strings.xml b/OsmAnd-telegram/res/values-ast/strings.xml new file mode 100644 index 0000000000..1d419eb73c --- /dev/null +++ b/OsmAnd-telegram/res/values-ast/strings.xml @@ -0,0 +1,153 @@ + +Altitú media + Velocidá media + Mapa + Amestar + Anubrir + Estáu + Desactivar + Guardar + Nome + Colar + Zarrar + Non + Instalar + Compartir + Atrás + Siguir + Encaboxar + Axustes + yd + ft + mi + km + m + nmi + min/m + min/km + m/s + km/h + mph + Quilómetros per hora + Milles per hora + Metros per segundu + Minutos per quilómetru + Minutos per milla + Milles/pies + Milles/yardes + Quilómetros/metros + Milles náutiques + Milles/metros + La supervisión ta activada + La supervisión ta desactivada + Data de fin + Data d\'aniciu + Activa la supervisión pa recoyer en segundu planu los datos de la movición. + Unviar l\'allugamientu como + Escueyi cómo van vese los mensaxes col to allugamientu. + Testu + Mapa y testu + L\'últimu anovamientu dende Telegram + Escueyi un nome que nun usares yá + Amestóse %1$s. + Nun pudo amestase\'l preséu nuevu + El nome del preséu nuevu, 200 caráuteres como máximu. + El nome del preséu ye perllargu + El nome del preséu nun pue tar baleru + Nome del preséu + Pues crear y ver la ID del preséu nun veceru pa Telegram usando la charra col robó %1$s .%2$s + Si quies coneutar munchos preseos a una cuenta de Telegram, precises usar un preséu diferente al compartir l\'allugamientu. + Esperando pola rempuesta de Telegram + Aniciando + Posicionando… + Camuda los axustes de la optimización de la batería pa estabilizar la compartición del allugamientu. + Trabayu en segundu planu + Compartición en segundu planu + Dir a Axustes + Más sero + Compartición: Activada + Nun hai conexón al GPS + Nun hai conexón a internet + Amestar un preséu + Contautos y grupos que tán compartiendo l\'allugamientu contigo. + ¿De xuru que quies zarrar sesión n\'OsmAnd Telegram pa que nun pueas compartir l\'allugamientu nin ver el d\'otros\? + ¿Zarrar la sesión d\'OsmAnd Telegram\? + pola distancia + pol nome + pol grupu + Ordenar por + Esbilla la versión d\'OsmAnd onde van amosase los contautos nel mapa. + Desactiva la compartición del allugamientu en toles charres esbillaes ((%1$d). + Desactivar toles comparticiones + Desactivar too + Grupu + Pa revocar l\'accesu a la compartición del allugamientu. Abri Telegram, vete a Axustes → Privacidá y seguranza → Sesiones y zarra la sesión d\'OsmAnd Telegram. + Cómo desactivar la compartición del allugamientu con OsmAnd dende Telegram + Cómo desactivar la compartición del allugamientu con OsmAnd dende Telegram + Cuenta coneutada + Cuenta + en %1$s + Escueyi la versión d\'OsmAnd qu\'OsmAnd Telegram va usar p\'amosar les posiciones. + Conexón a OsmAnd + Anubre los contautos que nun se movieren nel tiempu apurríu. + Historial d\'allugamientos + La última vez qu\'un contautu se movió. + Ausencia de movición + Afita l\'intervalu mínimu pa la compartición del allugamientu. + Unviar el mio allugamientu + Posición + Tiempu de la compartición + Caduca a les + Desactivar la compartición del allugamientu + Abrir OsmAnd + En direuto + Robó + Rexistru en Telegram + Precises una cuenta de Telegram pa usar la compartición d\'allugamientuos. + Instala Telegram y configura una cuenta. + Darréu vas poder usar esta aplicación. + Too + Nun teo una cuenta de Telegram + Introduz el númberu telefónicu + Introduz el códigu d\'autentación + %1$d h %2$d m + %1$d m + %1$d h + Visibilidá xeneral demientres + Afitamientu del tiempu + Esbilla los contautos y grupos colos que quies compartir el to allugamientu. + OsmAnd Telegram + Númberu telefónicu + Númberu telefónicu nel formatu internacional + Contraseña + Códigu d\'autenticación + Telegram unvióte un códigu pa OsmAnd pa qu\'anicies sesión na to cuenta. + Introduz la contraseña + Contraseña de Telegram + Zarrar sesión + ¿Activar «Allugamientu»\? + Nun aniciesti sesión + Activa «Allugamientu» nos axustes del sistema + OsmAnd Telegram execútase en segundu planu cola pantalla apagada. + Distancia + Compartiendo l\'allugamientu + Serviciu d\'OsmAnd Telegram + Logu d\'OsmAnd + Primero precises instalar la versión de baldre o de pagu d\'OsmAnd + Instalación d\'OsmAnd + Autorización + Introduz el to númberu telefónicu de Telegram nel formatu internacional + Afáyate + nmi/h + Milles náutiques per hora (nuedos) + h + m + s + La compartición del allugamientu con OsmAnd déxate compartir el to allugamientu y ver el d\'otros n\'OsmAnd.

L\'aplicación usa l\'API de Telegram y vas precisar una cuenta de Telegram.
+ El mio allugamientu + En direuto + Llinia temporal + L\'últimu allugamientu disponible + Estáu de la compartición + Afita\'l tiempu nel que los contautos y grupos esbillaos van ver el to allugamientu en tiempu real. +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-be/strings.xml b/OsmAnd-telegram/res/values-be/strings.xml new file mode 100644 index 0000000000..84837b8480 --- /dev/null +++ b/OsmAnd-telegram/res/values-be/strings.xml @@ -0,0 +1,190 @@ + +Апошняе абнаўленне з Telegram + Абярыце імя, якое вы яшчэ не выкарыстоўвалі + %1$s дададзена. + Дадаць + Не атрымалася дадаць новую прыладу + Максімальная даўжыня назвы новай прылады - 200 сімвалаў. + Назва прылады занадта доўгая + Назва прылады не можа быць пустой + Назва прылады + Схаваць + Вы можаце стварыць і праглядзець ідэнтыфікатар прылады ў кліенце Тэлеграма, выкарыстоўваючы %1$s бота. %2$s + Калі вы хочаце падлучыць некалькі прылад да аднаго акаўнта тэлеграм, то вам неабходна выкарыстаць розныя прылады, каб падзяліцца месцазнаходжаннем. + Апошняе абнаўленне месцазнаходжання: + Паспяхова адпраўлена і абноўлена + Немагчыма адправіць у размову Тэлеграм: + Чаканне адказу ад Тэлеграм + Адпраўленне месцазнаходжання + Запуск + Пазіцыянаванне… + Злучэнне з Інтэрнэтам + Змена параметраў аптымізацыі батарэі для стабілізацыі абмену інфармацыяй аб месцазнаходжанні. + Праца ў фонавым рэжыме + Выключыць аптымізацыю батарэі для OsmAnd Telegram, каб прадухіліць нечаканае выключэнне фонавага рэжыму. + Абмен у фонавым рэжыме + Перайсці ў налады + Пазней + Яшчэ не адпраўлена + Яшчэ не знойдзена + Пераадправіць звесткі аб месцазнаходжанні + Апошняе даступнае месца + "Статус абмену " + Абмен: Уключаны + Статус + Злучэнне з GPS адсутнічае + Злучэнне з Інтэрнэтам адсутнічае + Выключыць + Захаваць + Дадаць прыладу + Падзяліцца месцазнаходжаннем як + Кантакты і групы для абмену месцазнаходжаннем. + Вы не зможаце падзяліцца сваім месцазнаходжаннем і ўбачыць месцазнаходжанне іншых. Сапраўды выйсці з OsmAnd Telegram\? + Выйсці з OsmAnd Telegram\? + Імя + Па адлегласці + Па імёнах + Па групе + Упарадкаваць + Упарадкаваць па + Абярыце версію OsmAnd, у якой кантакты будуць адлюстроўвацца на мапе. + Абярыце версію OsmAnd для выкарыстання + Выключыць абмен для ўсіх абраных размоў (%1$d). + Выключыць усе абмены + Выключыце ўсе + Выйсці + таму + Апошні адказ + Група + Падлучыцеся да Інтэрнэту, каб карэктна выйсці з Тэлеграм. + Закрыць + Для таго, каб скасаваць абмен месцазнаходжаннем, адкрыйце Тэлеграм, перайдзіце ў Налады → Прыватнасць і бяспека → Сеансы і спыніце сеанс OsmAnd Telegram. + Як выключыць абмен месцазнаходжаннем у OsmAnd праз Тэлеграм + Як выключыць абмен месцазнаходжаннем у OsmAnd праз Тэлеграм + Падлучаны акаўнт + Рахунак + у %1$s + Абраць версію OsmAnd, якую OsmAnd Telegram будзе выкарыстоўваць для адлюстравання пазіцыі. + Злучэнне з OsmAnd + Схаваць кантакты, якія не перамяшчаліся пэўны час. + Гісторыя месцазнаходжанняў + Апошні раз кантакт рухаўся. + Не рухаецца + Вызначыць мінімальны інтэрвал для абмену інфармацыяй аб месцазнаходжанні. + Адправіць маё месцазнаходжанне + Пазіцыя + "Час абмену " + Сыходзіць + Абмен уключаны (выключыць) + Выключыць абмен + Адкрыць OsmAnd + Дзейных + Бот + Рэгістрацыя ў Telegram + Для абмену вам неабходны акаўнт Тэлеграм. + Калі ласка, ўсталюйце Тэлеграм і наладзьце акаўнт. + Пасля гэтага вы зможаце выкарыстоўваць дадатак. + Усе + Выкл + Вам неабходна мець акаўнт Тэлеграм і нумар тэлефона + У мяне няма акаўнта Тэлеграм + Увядзіце нумар тэлефона + Увядзіце код аўтарызацыі + Вызначце бачны час для ўсіх + %1$d г %2$d хв + %1$d хв + %1$d г + Усталяваць + Падзяліцца + Назад + Бачны для ўсіх час + Задайце час, што будуць бачыць абраныя вамі кантакты і групы ў рэжыме рэальнага часу. + Задаць час + Абярыце кантакты і групы, з якімі хочаце абменьвацца вашым месцазнаходжаннем. + Пошук: група альбо кантакт + Падзяліцца месцазнаходжаннем + Паказаць на мапе + ОsmAnd Telegram + Нумар тэлефона + Нумар тэлефона ў міжнародным фармаце + Пароль + Увядзіце код + Код аўтэнтыфікацыі + Тэлеграм адправіў вам код для OsmAnd для ўваходу ў акаўнт. + Увядзіце пароль + Пароль Тэлеграм + Увайсці + Выйсці + Запуск + Выхад + Закрыццё + Уключыць \"Месцазнаходжанне\"\? + Вы не ўвайшлі + Працягнуць + Скасаваць + Налады + Дадатак не мае дазволу на доступ да даных аб месцазнаходжанні. + Калі ласка, ўключыце \"Месцазнаходжанне\" ў сістэмных наладах + Абярыце аднаго пастаўшчыка месцазнаходжання, каб падзяліцца сваім месцазнаходжаннем. + Фонавы рэжым + OsmAnd Telegram працуе ў фонавым рэжыме з выключаным экранам. + Адлегласць + Падзяліцца месцазнаходжаннем + Абмен данымі аб месцазнаходжанні + Сэрвіс OsmAnd Telegram + Лагатып OsmAnd + Спачатку вам неабходна ўсталяваць бясплатную ці платную версію OsmAnd + Усталяваць OsmAnd + Паказаць карыстальнікаў на мапе + Актыўныя размовы + Аўтарызацыя + Калі ласка, увядзіце ваш нумар тэлефона, звязаны з Тэлеграм, ў міжнародным фармаце + Вітаем + + ярд + фут + міл + км + м + м.мілі + хв/м + хв/км + м.міль/г + м/с + км/г + м/г + Кіламетраў за гадзіну + Міляў за гадзіну + Метраў за секунду + Хвілін на кіламетар + Хвілін на мілю + Марскіх міль за гадзіну (вузлоў) + Мілі/футы + Мілі/ярды + Кіламетры/метры + Марскія мілі + Мілі/метры + г + хвіл + сек + Абмен месцазнаходжаннем OsmAnd Дае магчымасць дзяліцца сваім месцазнаходжаннем і бачыць месцазнаходжанне іншых у OsmAnd.

Дадатак выкарыстоўвае Telegram API, таму вам неабходны акаўнт Тэлеграм.
+ Маё месцазнаходжанне + Зараз дзейнічае + + +Адправіць месцазнаходжанне як + Абярыце як будуць выглядаць паведамленні з вашым месцазнаходжаннем. + Мапа + Тэкст + Мапа і тэкст + Маніторынг уключаны + Маніторынг адключаны + час руху + Сярэдняя вышыня + Сярэдняя хуткасць + Адкрыць у OsmAnd + Дата закрыцця + Час адкрыцця + Уключаць маніторынг для збору данных руху ў фоне. + Храналогія +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-cs/strings.xml b/OsmAnd-telegram/res/values-cs/strings.xml new file mode 100644 index 0000000000..a6b3daec93 --- /dev/null +++ b/OsmAnd-telegram/res/values-cs/strings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-da/strings.xml b/OsmAnd-telegram/res/values-da/strings.xml index 9dcf1ad420..cfc4bb1a31 100644 --- a/OsmAnd-telegram/res/values-da/strings.xml +++ b/OsmAnd-telegram/res/values-da/strings.xml @@ -1,28 +1,21 @@ - -Skift indstillinger for batterioptimering, for stabil deling af placering + +Skift indstillinger for batterioptimering, for stabil deling af placering. Baggrundstilstand - Sluk for batterioptimering af OsmAnd Telegram, så det ikke pludselig slukker, når i baggrunden. + Sluk for batterioptimering af OsmAnd Telegram, så det ikke pludseligt bliver slukket, når det er i baggrunden. Deling i baggrunden Gå til indstillinger Senere Ikke sendt endnu Ikke fundet endnu Send placering igen - Sidst sendte placering Sidste tilgængelige placering Delingsstatus Deling: aktiveret Status Ingen GPS-forbindelse - Sendt korrekt og opdateret - Ikke muligt at sende til samtaler: Ingen internetforbindelse Deaktiver - Gem - Indtast enheds-id, som kan findes på https://live.osmand.net/device/ID - Enheds-id Tilføj enhed - Giver mulighed for at dele enhedens placering, eller placering af brugerdefinerede enheder det tilføjes via API. Del placering som Kontakter og grupper som der deles placering med. Er du sikker på at du vil logge ud af OsmAnd Telegram, så du ikke kan dele din placering eller se placeringen af andre\? @@ -33,16 +26,16 @@ Efter gruppe Sorter Sorter efter - Vælg OsmAnd version, hvor kontakter skal vises på kortet. - Vælg en OsmAnd-version - Deaktiverer deling af placering for alle de valgte samtaler (%1$d). + Vælg den OsmAnd version, der skal vise kontakter på kortet. + Vælg OsmAnd-version der skal bruges + Slukker for placeringsdeling for alle de valgte samtaler (%1$d). Deaktiver al deling Sluk alt Afslut siden Sidste svar Gruppe - Opret forbindelse til internettet for at logge ud af Telegram. + Opret forbindelse til Internettet for at logge ud af Telegram. Luk "Sådan tilbagekaldes adgang til placeringsdeling. Åbn Telegram, gå til Settings - Privacy and Security - Sessions og afslut OsmAnd Telegram session. " Hvordan afbrydes OsmAnd placeringsdeling fra Telegram @@ -51,29 +44,28 @@ Konto i %1$s Vælg den OsmAnd version, som OsmAnd Telegram bruger til vise placeringer på kortet. - OsmAnd forbindelse - Skjul de kontaktpersoner, der ikke har opdateret placering i et givet tidsrum. + Tilslut OsmAnd + Skjul kontakter, der ikke har bevæget sig i en given tid. Placeringshistorik - Sidste gang en kontakt har sendt en placering. - Uaktuel placering + Sidste gang en kontakt har bevæget sig. + Bevæger sig ikke Angiv den mindste interval for deling af placering. Send placering - GPS og placering - Delingstidsrum + Placering + Delingstid Udløber - Deling er aktiveret (deaktiver) Sluk for deling af placering Åbn OsmAnd Live Bot Registrering i Telegram - Det er nødvendigt med en Telegram-konto for at bruge OsmAnd placeringsdeling. - Installer Telegram fra Google Play, og opret en konto. - Når konto er oprettet, kan programmet bruges. + Det er nødvendigt med en Telegram-konto for at bruge placeringsdeling. + Installer Telegram og opret en konto. + Så kan programmet bruges. Alle Fra Der er brug for en registreret Telegram-konto og telefonnummer - Jeg har ikke Telegram konto + Har ikke Telegram konto Indtast telefonnummer Indtast autentificeringskode Sæt synlige tid for alle @@ -83,12 +75,12 @@ Installer Del Tilbage - Synlige tid for alle - Sæt tidrummet hvor de valgte kontaktpersoner og grupper kan placering i realtid. + Synlig tid for alle + Sæt tiden hvor de valgte kontakter og grupper kan se placering i realtid. Sæt tid - Vælg de kontaktpersoner og grupper, som placeringen skal deles med. + Vælg de kontakter og grupper, som placeringen skal deles med. Søg: gruppe eller kontakt - Start deling af placering + Del placering Vis på kort OsmAnd Telegram Telefonnummer @@ -98,30 +90,21 @@ Autentificeringkode Telegram har sendt en kode. Den vil blive brugt af OsmAnd som konto log ind. Indtast adgangskode - Indtast adgangskode til Telegram-konto + Telegram adgangskode Log ind Log ud Ikke logget ind - Initialisering - Logger ud - Lukker Fortsæt Annuller - Placeringtjenesten er ikke aktiveret. Aktiver\? Indstillinger Mangler tilladelse til at bruge placeringsdata. - Aktiver GPS i indstillinger + Tænd for \"Placering\" i systemindstillinger Vælg en af placeringsudbyderne til at dele placering. Baggrundstilstand OsmAnd Telegram kører i baggrunden med skærmen slukket. Afstand Del placering Deler placering - Pauset - Ingen data - Pause - Start - Stop OsmAnd Telegram tjeneste OsmAnd logo Det er nødvendigt at installere en gratis eller betalt version af OsmAnd @@ -129,7 +112,7 @@ Vis brugere på kortet Aktive samtaler Autorisation - Indtast telefonnummer på Telegram-konto i internationalt format + Indtast Telegram telefonnummer i internationalt format Velkommen yd @@ -158,18 +141,48 @@ t min sek - + - OsmAnd placeringsdeling giver mulighed for at dele placering og se placering af andre i OsmAnd.

Programmet bruger Telegram API og skal have en Telegram konto.
+ OsmAnd placeringsdeling giver mulighed for at dele placering og se placering af andre i OsmAnd.

Programmet bruger Telegram API og skal bruge en Telegram konto.
Placering - Sender nu + - -Sidste ajourførte placering: + Sidste ajourførte placering: Sendt korrekt og opdateret Ikke muligt at sende til Telegram samtaler: Venter på svar fra Telegram - Sender placeringsmeddelelser - Initialiserer - Søger efter GPS - Forbinder til internettet -
+ Sender placering + Starter + Positionering… + Forbinder til Internettet + Vælg et navn der ikke allerede er brugt + %1$s tilføjet. + Tilføj + Kunne ikke tilføje ny enhed + Indtast et navn til den nye enhed. Max 200 tegn. + Enhedsnavn er for langt + Enhedsnavn må ikke være tomt + Enhedsnavn + Skjul + Opret og få vist enhedens-id i Telegram-klienten ved hjælp af %1$s chat-bot. %2$s + Hvis der skal tilsluttes flere enheder til en Telegram-konto, er det nødvendigt til at bruge forskellige enheder til at dele placeringen. + Gem + Deling er aktiv (sluk) + Starter + Logger ud + Lukker + Tænd for \"Placering\"\? + Live now + + +Sidste opdatering fra Telegram + Send placering som + Vælg hvordan beskeder med placering skal se ud. + Kort + Tekst + Kort og tekst + Gennemsnitshøjde + Gennemsnitshastighed + Åbn i OsmAnd + Slutdato + Startdato + Tidslinje +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-de/strings.xml b/OsmAnd-telegram/res/values-de/strings.xml index 9904a15b68..5c83236784 100644 --- a/OsmAnd-telegram/res/values-de/strings.xml +++ b/OsmAnd-telegram/res/values-de/strings.xml @@ -1,21 +1,15 @@ - + Teilen im Hintergrund Einstellungen öffnen Noch nicht gesendet Noch nicht gefunden Position neu senden - Letzter gesendeter Standort Freigabestatus Teilen: Aktiviert Status Kein GPS-Signal - Gesendet und aktualisiert - Konnte nicht an Chats senden: Keine Internetverbindung Deaktivieren - Speichern - Finden Sie Ihre Geräte-ID unter https://live.osmand.net/device/ID - Geräte-ID Gerät hinzufügen Später Letzter verfügbarer Standort @@ -28,7 +22,7 @@ Nach Gruppe Sortieren Sortieren nach - Wählen Sie die OsmAnd-Version, in der die Kontakte auf der Karte angezeigt werden. + Wählen Sie die OsmAnd-Version, in der Kontakte auf der Karte angezeigt werden. Zu verwendende OsmAnd-Version auswählen Alle ausschalten Beenden @@ -40,19 +34,19 @@ Verbundenes Konto Konto "in %1$s " - Wählen Sie die OsmAnd-Version, die OsmAnd Telegram verwendet, um Positionen auf der Karte anzuzeigen. + Wählen Sie die OsmAnd-Version, die OsmAnd Telegram verwendet, um Positionen anzuzeigen. OsmAnd verbinden - Blenden Sie die Kontakte aus, die ihren Standort in einer vorgegebenen Zeitspanne nicht aktualisiert haben. + Blendet Kontakte aus, die sich eine bestimmte Zeit lang nicht bewegt haben. Standortverlauf - Das letzte Mal, dass ein Kontakt seinen Standort gesendet hat. + Das letzte Mal, dass sich ein Kontakt bewegt hat. Meinen Standort senden - GPS & Standort + Position OsmAnd öffnen "Live " Registrierung in Telegram - Sie benötigen ein Telegram-Konto, um die OsmAnd Standortfreigabe nutzen zu können. - Bitte installieren Sie Telegram von Google Play und registrieren Sie dazu ein Konto. - Nach der Erstellung des Kontos können Sie diese App nutzen. + Sie benötigen ein Telegram-Konto, um die Standortfreigabe nutzen zu können. + Bitte installieren Sie Telegram und richten Sie ein Konto ein. + Dann können Sie diese App nutzen. Alle Aus Sie benötigen ein registriertes Telegram-Konto und eine Telefonnummer @@ -71,33 +65,24 @@ Auf Karte anzeigen "OsmAnd Telegram " Telefonnummer - Telefonnummer im internationalen Format + Telefonnummer in internationalem Format Passwort Code eingeben Authentifizierungscode Telegram hat Ihnen einen Code für OsmAnd zur Anmeldung an Ihrem Konto gesendet. Passwort eingeben Anmelden - Abmeldung + Abmelden Sie sind nicht angemeldet - Initialisierung - Abmeldung - Schließen Fortfahren Abbrechen - Standortdienst einschalten\? Einstellungen Der App fehlt die Berechtigung, auf Standortdaten zuzugreifen. - Bitte aktivieren Sie GPS in den Einstellungen + Bitte schalten Sie \"Standort\" in den Systemeinstellungen ein Hintergrundmodus OsmAnd Telegram läuft im Hintergrund bei ausgeschaltetem Bildschirm. Distanz Standort teilen - Pausiert - Keine Daten - "Pause " - "Start " - "Stop " OsmAnd Telegram-Service OsmAnd-Logo Sie müssen zuerst die kostenlose oder kostenpflichtige Version von OsmAnd installieren @@ -132,44 +117,76 @@ h min s - + Mein Standort - Sende gerade Hintergrundbetrieb Erfolgreich gesendet und aktualisiert Warten auf Antwort von Telegram - Versand von Standortmitteilungen - Initialisierung - Suche nach GPS-Signal + Sende Standort + Startet + Positioniere..… Verbindung zum Internet - Akkuoptimierungseinstellungen für eine dauerhafte Standortfreigabe ändern - Bitte Passwort für Ihr Telegram-Konto eingeben + Akkuoptimierungseinstellungen, um die Standortfreigabe zu stabilisieren. + Telegram-Passwort kn kn/h Zuletzt aktualisierter Ort: Es ist nicht möglich, an Telegram-Chats zu senden: Schalten Sie die Batterieoptimierung für OsmAnd Telegram aus, damit es nicht plötzlich im Hintergrund ausgeschaltet wird. - Die gemeinsame Nutzung Ihres Geräte-Standorts oder des Standorts benutzerdefinierter Geräte erlauben, die über die API hinzugefügt wurden. Sind Sie sicher, dass Sie sich von OsmAnd Telegram abmelden möchten, so dass Sie den Standort nicht teilen oder den Standort anderer sehen können\? Schaltet die Standortfreigabe für alle ausgewählten Chats aus (%1$d). Alle Freigaben deaktivieren - Um den Zugriff auf die Standortfreigabe zu widerrufen, öffnen Sie Telegram, gehen zu Einstellungen - Datenschutz und Sicherheit - Sitzungen und beenden die OsmAnd Telegram-Sitzung. + Um den Zugriff auf die Standortfreigabe zu widerrufen, öffnen Sie Telegram, gehen zu Einstellungen → Datenschutz und Sicherheit → Sitzungen und beenden die OsmAnd Telegram-Sitzung. So deaktivieren Sie OsmAnd Standortfreigabe von Telegram aus So deaktivieren Sie OsmAnd Standortfreigabe von Telegram aus - Veralteter Standort + Keine Bewegung Legen Sie das Mindestintervall für die Standortfreigabe fest. Freigabezeit - Verfällt um - Freigabe ist aktiviert (deaktivieren) + Verfällt Standortfreigabe ausschalten Bot Für alle sichtbare Zeit einstellen Zeit einstellen, zu der Ihren Kontakten und Gruppen Ihr Standorts in Echtzeit angezeigt wird. - Kontakte und Gruppen wählen, für die Sie Ihren Standort freigeben möchten. - Standortfreigabe starten - Wählen Sie einen der Standortprovider aus, um Ihren Standort freizugeben. + Kontakte und Gruppen wählen, denen Sie Ihren Standort freigeben möchten. + Standort freigeben + Wählen Sie einen der Standortanbieter aus, um Ihren Standort freizugeben. Standort teilen OsmAnd Standortfreigabe ermöglicht es Ihnen, Ihren Standort zu teilen und den anderer in OsmAnd zu sehen.

Die App verwendet die API von Telegram und Sie benötigen ein Telegram-Konto.
-
+ Wählen Sie einen Namen, den Sie noch nicht benutzt haben + %1$s hinzugefügt. + Hinzufügen + Neues Gerät konnte nicht hinzugefügt werden + Benennen Sie Ihr neues Gerät mit max. 200 Symbolen. + Gerätename ist zu lang + Der Gerätename darf nicht leer sein + Gerätename + Verbergen + Sie können die Geräte-ID im Telegram-Client mit dem Chat-Bot %1$s erstellen und anzeigen. %2$s + Wenn Sie mehrere Geräte mit einem Telegram-Konto verbinden möchten, müssen Sie ein anderes Gerät verwenden, um Ihren Standort zu teilen. + Speichern + Die Freigabe ist eingeschaltet (ausschalten) + Startet + Abmeldevorgang + Schliesst + \"Standort\" einschalten\? + Jetzt live + + +Letztes Update von Telegram + Senden Standort als + Wählen Sie, wie Nachrichten mit Ihrem Standort aussehen sollen. + Karte + Text + Karte und Text + Überwachung aktiviert + Überwachung deaktiviert + Zeit in Bewegung + Durchschnittliche Höhe + Durchschnittliche Geschwindigkeit + In OsmAnd öffnen + Endzeit + Startzeit + Überwachung aktivieren, um Bewegungsdaten im Hintergrund zu erfassen. + Zeitachse + \ No newline at end of file diff --git a/OsmAnd-telegram/res/values-es-rUS/strings.xml b/OsmAnd-telegram/res/values-es-rUS/strings.xml index d3937cbc72..3c6782d1c2 100644 --- a/OsmAnd-telegram/res/values-es-rUS/strings.xml +++ b/OsmAnd-telegram/res/values-es-rUS/strings.xml @@ -1,34 +1,26 @@ - -Cambiar los ajustes de optimización de la batería, para compartir una ubicación estable + +Cambiar los ajustes de optimización de la batería para estabilizar la ubicación compartida. Funcionamiento en segundo plano - Para un uso compartido estable de su posición en segundo plano, es aconsejable desactivar la optimización de la batería para OsmAnd Telegram. -\n -\nSi la optimización está activada, el sistema puede desactivar automáticamente la aplicación que se está ejecutando en segundo plano (con la pantalla bloqueada y/o la aplicación minimizada). Esto sucede sin notificación y hace que la transmisión de la posición geográfica se detenga. + Desactivar la optimización de la batería para OsmAnd Telegram de modo que no se desconecte repentinamente cuando esté en segundo plano. Compartir en segundo plano Ir a los ajustes Luego No enviado aún Aún no encontrada Reenviar ubicación - Última ubicación enviada Última ubicación disponible Estado de compartición Compartir: Activado Estado Sin conexión GPS - Enviado y actualizado con éxito - Imposible enviar a los chats: Sin conexión a Internet Desactivar Guardar - Ingrese su ID de dispositivo que puede encontrar en https://live.osmand.net/device/ID - Id. del dispositivo Añadir dispositivo - Permite compartir la ubicación de este dispositivo o la ubicación del dispositivo personalizado añadido a través de dispositivos API. Compartir ubicación como - Contactos y grupos que comparten su ubicación con usted. - Si cierra sesión en OsmAnd Telegram, no podrá enviar su posición ni ver la ubicación de sus contactos en el mapa de OsmAnd. - ¿Cerrar sesión en OsmAnd Telegram\? + Contactos y grupos que comparten la ubicación contigo. + ¿Cerrar sesión en OsmAnd Telegram\? No podrá compartir la ubicación o ver la ubicación de otros + ¿Cerrar sesión de OsmAnd Telegram\? Nombre Por distancia Por nombre @@ -37,44 +29,43 @@ Ordenar por Seleccione la versión de OsmAnd donde los contactos se mostrarán en el mapa. Seleccione la versión de OsmAnd para usar - Dejará de compartir la ubicación con todos los chats seleccionados (%1$d). + Desactivar la ubicación compartida en todos los chats seleccionados (%1$d). Desactivar el uso compartido Apagar todos Salir hace Última respuesta Grupo - Para desconectarse correctamente de su cuenta de Telegram, se necesita Internet. + Conéctese a Internet para cerrar sesión en Telegram correctamente. Cerrar - Para revocar el acceso a su cuenta de Telegram desde OsmAnd, abra Telegram, vaya a «Ajustes - Privacidad y Seguridad - Sesiones» y termine la sesión de Telegram de OsmAnd. Después de eso, «OsmAnd Location Sharing» ya no tendrá acceso a su cuenta y no podrá utilizar esta aplicación hasta que vuelva a iniciar sesión. - Cómo desconectar «OsmAnd Location Sharing» de la cuenta de Telegram - Cómo desconectar «OsmAnd Location Sharing» de la cuenta de Telegram + Para revocar el acceso a la ubicación compartida. Abra Telegram, vaya a «Ajustes → Privacidad y Seguridad → Sesiones» y cierre sesión de OsmAnd Telegram. + Cómo desactivar la «Ubicación Compartida de OsmAnd» desde Telegram + Cómo desactivar la «Ubicación Compartida de OsmAnd» desde Telegram Cuenta conectada Cuenta en %1$s - Elija la versión de OsmAnd a la que se referirá «OsmAnd Telegram» para mostrar la ubicación en el mapa. + Elija la versión de OsmAnd el cual «OsmAnd Telegram» utilizará para mostrar las ubicaciones. Conectar OsmAnd - Ocultar los contactos que no hayan actualizado su ubicación después del período de tiempo especificado. + Ocultar los contactos que no se han movido en un tiempo determinado. Historial de ubicación - La última vez que un contacto envió una actualización a su ubicación. - Ubicación obsoleta + La última vez que un contacto se movió. + Sin movimiento Fijar el intervalo mínimo para compartir la ubicación. Enviar mi ubicación - GPS y ubicación + Ubicación Tiempo compartido Expira en - El uso compartido está activado (desactivar) - Desactivar el uso compartido de ubicaciones + Desactivar la ubicación compartida Abrir OsmAnd En vivo Bot Registro en Telegram - Necesita una cuenta en Telegram para utilizar las capacidades de «OsmAnd Location Sharing». - Si desea continuar, instale Telegram de Google Play y registre su cuenta. - Después de crear una cuenta, puede utilizar esta aplicación. + Necesita una cuenta en Telegram para utilizar la ubicación compartida. + Instale Telegram y cree una cuenta. + Después podrá utilizar esta aplicación. Todos No - Necesita una cuenta ya registrada y un número de teléfono en Telegram + Necesita una cuenta registrada en Telegram y un número de teléfono No tengo cuenta de Telegram Ingrese el número de teléfono Ingrese el código de autenticación @@ -89,8 +80,8 @@ Ajustar la hora en que los contactos y grupos marcados verán la ubicación en tiempo real. Fijar la hora Marque los contactos y grupos con los que desea compartir su ubicación. - Buscar: grupo o contacto - Iniciar el uso compartido de ubicaciones + Buscar: Grupo o contacto + Compartir ubicación Mostrar en el mapa OsmAnd Telegram Número de teléfono @@ -98,40 +89,32 @@ Contraseña Ingresar código Código de autenticación - Se ha enviado un código a Telegram. Será utilizado por OsmAnd para acceder a su cuenta. + Telegram ha enviado un código para que OsmAnd inicie sesión en su cuenta. Ingresar contraseña - Por favor, ingrese la contraseña de su cuenta de Telegram para finalizar la autorización + Contraseña de Telegram Iniciar sesión Cerrar sesión No ha iniciado sesión - Inicialización - Cerrar sesión - Cerrando Continuar Cancelar - Servicio de ubicación desactivado. ¿Quieres activarlo\? + ¿Activar «Ubicación»\? Ajustes - La aplicación no tiene permiso de acceso a los datos de ubicación. - Activa el GPS en ajustes - El servicio de ubicación compartida requiere que se active un proveedor de ubicación. + La aplicación no tiene permiso para acceder a los datos de ubicación. + Activar «Ubicación» en los ajustes del sistema + Elija uno de los proveedores de ubicación para compartir su ubicación. Modo reposo OsmAnd Telegram se ejecuta en modo reposo con la pantalla apagada. Distancia Compartir ubicación Compartir ubicación - Pausado - Sin datos - Pausar - Iniciar - Parar Servicio de OsmAnd Telegram Logotipo de OsmAnd - Necesita instalar primero una versión gratuita o de pago de OsmAnd + Necesita instalar primero la versión gratuita o de pago de OsmAnd Instalar OsmAnd Mostrar usuarios en el mapa Chats activos Autorización - Por favor, introduzca el número de teléfono de su cuenta de Telegram en el formato internacional + Ingrese el número de teléfono de Telegram en el formato internacional Bienvenido yd @@ -160,10 +143,54 @@ h min seg - + - OsmAnd Location Sharing permite compartir su ubicación y ver las ubicaciones de otras personas en OsmAnd.

La aplicación funciona sobre la base de Telegram API. Para utilizar esta aplicación debe tener una cuenta de Telegram.
+ La Ubicación Compartida de OsmAnd (en inglés, «OsmAnd Location Sharing») permite compartir su ubicación y ver las ubicaciones de otras personas en OsmAnd.

La aplicación funciona sobre la base de Telegram API. Para utilizar esta aplicación debe tener una cuenta de Telegram.
Mi ubicación En vivo ahora -
+Elige un nombre que no se haya usado + %1$s añadido(s). + Añadir + No se pudo añadir el dispositivo + El nombre del dispositivo nuevo (máx. 200 símbolos). + Nombre del dispositivo demasiado largo + El nombre del dispositivo no debe estar vacío + Nombre del dispositivo + Ocultar + Puede crear y ver el ID de dispositivo en el cliente de Telegram usando el chat bot %1$s. %2$s + Si desea conectar varios dispositivos a una cuenta de Telegram, debe utilizar un dispositivo diferente para compartir su ubicación. + Última ubicación actualizada: + Enviado y actualizado con éxito + Imposible enviar a los chats de Telegram: + Esperando la respuesta de Telegram + Ubicación de envío + Iniciando + Ubicando… + Conexión a Internet + Última actualización de Telegram + Compartir está activado (desactivar) + Iniciando + Cerrar sesión + Cerrando + Enviar ubicación como + Elige cómo se verán los mensajes con tu ubicación. + Mapa + Texto + Mapa y texto + Monitorización activada + Monitorización desactivada + tiempo en movimiento + Altitud media + Velocidad media + Abrir en OsmAnd + Fecha de finalización + Fecha de inicio + Activar la monitorización para recopilar datos de movimiento en segundo plano. + Línea de tiempo + enviado (%1$d en búfer) + %1$d puntos + Fecha + Recolectados + Puntos de GPS + Enviado +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-es/strings.xml b/OsmAnd-telegram/res/values-es/strings.xml index efa62e32db..ec447a77ab 100644 --- a/OsmAnd-telegram/res/values-es/strings.xml +++ b/OsmAnd-telegram/res/values-es/strings.xml @@ -1,34 +1,25 @@ - -Cambiar los ajustes de optimización de la batería, para compartir una ubicación estable + +Cambiar los ajustes de optimización de la batería para estabilizar la ubicación compartida. Funcionamiento en segundo plano - Para un uso compartido estable de su posición en segundo plano, es aconsejable desactivar la optimización de la batería para OsmAnd Telegram. -\n -\nSi la optimización está activada, el sistema puede desactivar automáticamente la aplicación que se está ejecutando en segundo plano (con la pantalla bloqueada y/o la aplicación minimizada). Esto sucede sin notificación y hace que la transmisión de la posición geográfica se detenga. + Desactivar la optimización de la batería para OsmAnd Telegram de modo que no se desconecte repentinamente cuando esté en segundo plano. Compartir en segundo plano Ir a los ajustes Luego No enviado aún Aún no encontrada Reenviar ubicación - Última ubicación enviada Última ubicación disponible Estado de compartición Compartir: Activado Estado Sin conexión GPS - Enviado y actualizado con éxito - Imposible enviar a los chats: Sin conexión a Internet Desactivar - Guardar - Ingrese su ID de dispositivo que puede encontrar en https://live.osmand.net/device/ID - Id. del dispositivo Añadir dispositivo - Permite compartir la ubicación de este dispositivo o la ubicación del dispositivo personalizado añadido a través de dispositivos API. Compartir ubicación como - Contactos y grupos que comparten su ubicación con usted. - Si cierra sesión en OsmAnd Telegram, no podrá enviar su posición ni ver la ubicación de sus contactos en el mapa de OsmAnd. - ¿Cerrar sesión en OsmAnd Telegram\? + Contactos y grupos que comparten la ubicación contigo. + ¿Cerrar sesión en OsmAnd Telegram\? No podrá compartir la ubicación o ver la ubicación de otros + ¿Cerrar sesión de OsmAnd Telegram\? Nombre Por distancia Por nombre @@ -37,44 +28,43 @@ Ordenar por Seleccione la versión de OsmAnd donde los contactos se mostrarán en el mapa. Seleccione la versión de OsmAnd para usar - Dejará de compartir la ubicación con todos los chats seleccionados (%1$d). + Desactivar la ubicación compartida en todos los chats seleccionados (%1$d). Desactivar el uso compartido Apagar todos Salir hace Última respuesta Grupo - Para desconectarse correctamente de su cuenta de Telegram, se necesita Internet. + Conéctese a Internet para cerrar sesión en Telegram correctamente. Cerrar - Para revocar el acceso a su cuenta de Telegram desde OsmAnd, abra Telegram, vaya a «Ajustes - Privacidad y Seguridad - Sesiones» y termine la sesión de Telegram de OsmAnd. Después de eso, «OsmAnd Location Sharing» ya no tendrá acceso a su cuenta y no podrá utilizar esta aplicación hasta que vuelva a iniciar sesión. - Cómo desconectar «OsmAnd Location Sharing» de la cuenta de Telegram - Cómo desconectar «OsmAnd Location Sharing» de la cuenta de Telegram + Para revocar el acceso a la ubicación compartida. Abra Telegram, vaya a «Ajustes → Privacidad y Seguridad → Sesiones» y cierre sesión de OsmAnd Telegram. + Cómo desactivar la «Ubicación Compartida de OsmAnd» desde Telegram + Cómo desactivar la «Ubicación Compartida de OsmAnd» desde Telegram Cuenta conectada Cuenta en %1$s - Elija la versión de OsmAnd a la que se referirá «OsmAnd Telegram» para mostrar la ubicación en el mapa. + Elija la versión de OsmAnd el cual «OsmAnd Telegram» utilizará para mostrar las ubicaciones. Conectar OsmAnd - Ocultar los contactos que no hayan actualizado su ubicación después del período de tiempo especificado. + Ocultar los contactos que no se han movido en un tiempo determinado. Historial de ubicación - La última vez que un contacto envió una actualización a su ubicación. - Ubicación obsoleta + La última vez que un contacto se movió. + Sin movimiento Fijar el intervalo mínimo para compartir la ubicación. Enviar mi ubicación - GPS y ubicación + Ubicación Tiempo compartido Expira en - El uso compartido está activado (desactivar) - Desactivar el uso compartido de ubicaciones + Desactivar la ubicación compartida Abrir OsmAnd En vivo Bot Registro en Telegram - Necesita una cuenta en Telegram para utilizar las capacidades de «OsmAnd Location Sharing». - Si desea continuar, instale Telegram de Google Play y registre su cuenta. - Después de crear una cuenta, puede utilizar esta aplicación. + Necesita una cuenta en Telegram para utilizar la ubicación compartida. + Instale Telegram y cree una cuenta. + Después podrá utilizar esta aplicación. Todos No - Necesita una cuenta ya registrada y un número de teléfono en Telegram + Necesita una cuenta registrada en Telegram y un número de teléfono No tengo cuenta de Telegram Ingrese el número de teléfono Ingrese el código de autenticación @@ -89,8 +79,8 @@ Ajustar la hora en que los contactos y grupos marcados verán la ubicación en tiempo real. Fijar la hora Marque los contactos y grupos con los que desea compartir su ubicación. - Buscar: grupo o contacto - Iniciar el uso compartido de ubicaciones + Buscar: Grupo o contacto + Compartir ubicación Mostrar en el mapa OsmAnd Telegram Número de teléfono @@ -98,40 +88,31 @@ Contraseña Ingresar código Código de autenticación - Se ha enviado un código a Telegram. Será utilizado por OsmAnd para acceder a su cuenta. + Telegram ha enviado un código para que OsmAnd inicie sesión en su cuenta. Ingresar contraseña - Por favor, ingrese la contraseña de su cuenta de Telegram para finalizar la autorización + Contraseña de Telegram Iniciar sesión Cerrar sesión No ha iniciado sesión - Inicialización - Cerrar sesión - Cerrando Continuar Cancelar - Servicio de ubicación desactivado. ¿Quieres activarlo\? Ajustes - La aplicación no tiene permiso de acceso a los datos de ubicación. - Active el GPS en los ajustes - El servicio de ubicación compartida requiere que se active un proveedor de ubicación. + La aplicación no tiene permiso para acceder a los datos de ubicación. + Activar «Ubicación» en los ajustes del sistema + Elija uno de los proveedores de ubicación para compartir su ubicación. Modo en segundo plano OsmAnd Telegram se ejecuta en modo reposo con la pantalla apagada. Distancia Compartir ubicación Compartir ubicación - Pausado - Sin datos - Pausar - Iniciar - Parar Servicio de OsmAnd Telegram Logotipo de OsmAnd - Necesita instalar primero una versión gratuita o de pago de OsmAnd + Necesita instalar primero la versión gratuita o de pago de OsmAnd Instalar OsmAnd Mostrar usuarios en el mapa Chats activos Autorización - Por favor, introduzca el número de teléfono de su cuenta de Telegram en el formato internacional + Ingrese el número de teléfono de Telegram en el formato internacional Bienvenido yd @@ -160,10 +141,56 @@ h min seg - + - OsmAnd Location Sharing permite compartir su ubicación y ver las ubicaciones de otras personas en OsmAnd.

La aplicación funciona sobre la base de Telegram API. Para utilizar esta aplicación debe tener una cuenta de Telegram.
+ La Ubicación Compartida de OsmAnd (en inglés, «OsmAnd Location Sharing») permite compartir su ubicación y ver las ubicaciones de otras personas en OsmAnd.

La aplicación funciona sobre la base de Telegram API. Para utilizar esta aplicación debe tener una cuenta de Telegram.
Mi ubicación En vivo ahora -
+Elige un nombre que no se haya usado + %1$s añadido(s). + Añadir + No se pudo añadir el dispositivo + El nombre del dispositivo nuevo (máx. 200 símbolos). + Nombre del dispositivo demasiado largo + El nombre del dispositivo no debe estar vacío + Nombre del dispositivo + Ocultar + Puede crear y ver el ID de dispositivo en el cliente de Telegram usando el chat bot %1$s. %2$s + Si desea conectar varios dispositivos a una cuenta de Telegram, debe utilizar un dispositivo diferente para compartir su ubicación. + Última ubicación actualizada: + Enviado y actualizado con éxito + Guardar + Última actualización de Telegram + Imposible enviar a los chats de Telegram: + Esperando la respuesta de Telegram + Ubicación de envío + Iniciando + Ubicando… + Conexión a Internet + Compartir está activado (desactivar) + Iniciando + Cerrar sesión + Cerrando + ¿Activar «Ubicación»\? + Enviar ubicación como + Elija cómo se verán los mensajes con su ubicación. + Mapa + Texto + Mapa y texto + Monitorización activada + Monitorización desactivada + tiempo en movimiento + Altitud media + Velocidad media + Abrir en OsmAnd + Fecha de fin + Fecha de inicio + Activar la monitorización para recopilar datos de movimiento en segundo plano. + Línea de tiempo + enviado (%1$d en búfer) + %1$d puntos + Fecha + Recolectados + Puntos de GPS + Enviado +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-fa/strings.xml b/OsmAnd-telegram/res/values-fa/strings.xml new file mode 100644 index 0000000000..302ffb89d8 --- /dev/null +++ b/OsmAnd-telegram/res/values-fa/strings.xml @@ -0,0 +1,23 @@ + +آخرین به‌روزرسانی نسخهٔ تلگرام + %1$s اضافه شد. + افزودن + نمی‌توان دستگاه جدید اضافه کرد + نام دستگاه جدید خود را با حداکثر ۲۰۰ نویسه بنویسید. + نام دستگاه خیلی طولانی است + نام دستگاه نباید خالی باشد + نام دستگاه + اگر بخواهید چندین دستگاه را به یک حساب تلگرامی متصل کنید، باید موقعیت خود را با دستگاه دیگری به‌اشتراک بگذارید. + با موفقیت ارسال و به‌روز شد + در انتظار پاسخ از تلگرام + در حال موقعیت‌یابی… + بهینه‌سازی باتری را برای OsmAnd Telegram غیرفعال کنید تا هنگامی که در پس‌زمینه کار می‌کند ناگهان بسته نشود. + برو به تنظیمات + بعداً + هنوز ارسال نشده + هنوز پیدا نشده + بازفرستی موقعیت + اشتراک‌گذاری: فعال + وضعیت + بستن + diff --git a/OsmAnd-telegram/res/values-gl/strings.xml b/OsmAnd-telegram/res/values-gl/strings.xml index cecc5d9028..4abcdc0a85 100644 --- a/OsmAnd-telegram/res/values-gl/strings.xml +++ b/OsmAnd-telegram/res/values-gl/strings.xml @@ -1,30 +1,25 @@ - -Muda-los axustes de optimización da batería, para poñer a funciona-la ubicación de xeito estábel + +Muda-los axustes de optimización da batería, para estabilizares a ubicación compartillada. Funcionamento no segundo plano - Partillar ou compartir no segundo plano + Compartillar no segundo plano Ir ós axustes Máis tarde Aínda non enviado Aínda non atopado Voltar a enviar ubicación - Derradeira ubicación enviada - Derradeira ubicación dispoñíbel - Estado de emprego partillado ou compartido - Partillar ou compartir: Activado + Última ubicación dispoñíbel + Estado do compartillamento + Compartillar: Activado Estado Sen unha conexión GPS - Enviado e actualizado de xeito correcto - Non é posíbel enviar ás parolas: - Sen conexión á Internet + Sen unha conexión á Internet Desactivar Gardar - Insira o seu ID do dispositivo que pode atopar en https://live.osmand.net/device/ID - Identificador do dispositivo Engadir dispositivo - Partillar ou compartir ubicación coma - Contactos e grupos que partillan ou comparten a súa ubicación con vostede. - Se pecha a sesión no Telegram do OsmAnd, non poderá envia-la súa ubicación nin olla-las localizacións dos seus contactos no mapa do OsmAnd. - Pechar sesión do Telegram do OsmAnd\? + Compartillar ubicación coma + Contactos e grupos que están a compartilla-la súa ubicación para vostede. + Está na certeza de que desexa desconectarse do Telegram OsmAnd para non poder compartilla-la ubicación ou olla-la ubicación doutros\? + Pecha-la sesión do Telegram OsmAnd\? Nome Pola distancia Polo nome @@ -33,32 +28,163 @@ Ordenar polo Escolle a versión do OsmAnd onde os contactos amosaranse no mapa. Escolle a versión do OsmAnd para empregar - Deixarás de partillar ou comparti-la ubicación con tódalas parolas elixidas (%1$d). - Desactiva-lo emprego partillado ou compartido + Desactiva a ubicación compartillada en tódalas conversas elixidas (%1$d). + Desactiva-lo emprego compartillado Desactivar todos Saír hai - Derradeira resposta + Última resposta Grupo Pechar Conta conectada Conta en %1$s - Historial da localización + Historial da ubicación Envia-la miña localización - GPS e localización + Ubicación Abri-lo OsmAnd Ao vivo Rexistro no Telegram Todo - Desactivado - Non tes unha conta do Telegram - Insire o número de teléfono móbil - Insire o código de autenticación + Non + Non teño unha conta do Telegram + Insira o número do teléfono móbil + Insira o código da autentificación %1$d h %2$d min %1$d min %1$d h Instalar - Partillar ou compartir + Compartillar Voltar - + Engadir + Escolle un nome que aínda non está a ser empregado + %1$s engadido. + Desbotar + nmi + Quilómetros por hora + Millas por hora + Metros por segundo + Minutos por quilómetro + Minutos por milla + Millas/pés + Millas/iardas + Quilómetros/metros + Millas náuticas + Millas/metros + Última actualización do Telegram + Non foi posíbel engadi-lo dispositivo + O nome do novo dispositivo (máx. 200 símbolos). + Nome do dispositivo moi longo + O nome do dispositivo non pode ficar baleiro + Nome do dispositivo + Agochar + Pode xerar e olla-lo ID do dispositivo no cliente do Telegram empregando o chat bot %1$s. %2$s + Se desexa conectar varios dispositivos a unha conta do Telegram, ten que empregar un dispositivo diferente para compartilla-la súa ubicación. + Última ubicación actualizada: + Enviado e actualizado de xeito correcto + Non é posíbel enviar ás converas do Telegram: + Estase a agarda-la resposta do Telegram + Ubicación do envío + Estase a iniciar + Estase a posicionar… + Estase a conectar á Internet + Desactiva-la optimización da batería para o OsmAnd Telegram de xeito que non se desconecte de xeito súbito cando esté no segundo plano. + Conéctese á Internet para pecha-la sesión no Telegram de xeito correcto. + Para revoga-lo acceso á ubicación compartillada. Abra o Telegram, vaia cara ós «Axustes → Privacidade e Seguranza → Sesións» e peche a sesión do OsmAnd Telegram. + De que xeito desactiva-la «Ubicación compartillada do OsmAnd» dende o Telegram + De que xeito desactiva-la «Ubicación compartillada do OsmAnd» dende o Telegram + Escolla a versión do OsmAnd na cal o «OsmAnd Telegram» empregará para amosa-las ubicacións. + Conecta-lo OsmAnd + Agocha-los contactos que non se moveron nun tempo determinado. + A última vez que un contacto se moveu. + Sen movemento + Fixar intre mínimo para compartilla-la ubicación. + Hora compartillada + Remata en + O compartillamento fica activado (desactivar) + Desactiva-la ubicación compartillada + Bot + Precisa dunha conta no Telegram para emprega-la ubicación compartillada. + Instale o Telegram e cree unha conta. + Despois poderá empregar esta aplicación. + Precisa dunha conta rexistrada no Telegram e dun número de teléfono móbil + Axusta-la hora visíbel para todos + Hora visíbel para todos + Axusta-la hora na que os contactos e grupos marcados ollarán a ubicación en tempo real. + Fixar hora + Escolla os contactos e grupos cos que desexa compartilla-la súa ubicación. + Procurar: Grupo ou contacto + Compartillar ubicación + Amosar no mapa + OsmAnd Telegram + Número do teléfono móbil + Número do teléfono móbil no formato internacional + Contrasinal + Inserir código + Código da autentificación + O Telegram enviou un código para que o OsmAnd inicie sesión na súa conta. + Inserir contrasinal + Contrasinal do Telegram + Inicia-la sesión + Pecha-la sesión + Estase a iniciar + Estase a pecha-la sesión + Estase a pechar + Activar «Ubicación»\? + Non iniciou a sesión + Proseguer + Axustes + A aplicación non ten permiso para acceder ós datos da ubicación. + Activar «Ubicación» nos axustes do sistema + Escolla un dos fornecedores da ubicación para compartilla-la súa ubicación. + Modo no segundo plano + O OsmAnd Telegram execútase no modo en segundo plano ca pantalla apagada. + Distancia + Compartillar ubicación + Estase a compartilla-la ubicación + Servizo do OsmAnd Telegram + Logotipo do OsmAnd + Precísase instalar primeiro a versión de balde ou de pagamento do OsmAnd + Instala-lo OsmAnd + Amosar usuarios no mapa + Conversas activas + Autorización + Insira o número de teléfono móbil do Telegram no formato internacional + Benvido + + id + pés + mi + km + m + min/mi + min/km + nmi/h + m/seg + km/h + mi/h + Millas náuticas por hora (nós) + h + min + seg + A Ubicación compartillada do OsmAnd (en inglés, «OsmAnd Location Sharing») permite compartilla-la súa ubicación e olla-las ubicacións de outras persoas no OsmAnd.

A aplicación execútase sobre a base da API do Telegram. Para empregar esta aplicación ten que ter unha conta do Telegram.
+ A miña ubicación + Ao vivo agora + + +Enviar ubicación coma + Escolla de que xeito se verán as mensaxes ca súa ubicación. + Mapa + Texto + Mapa e texto + O monitoramento está activado + O monitoramento está desactivado + tempo en movemento + Altitude media + Velocidade media + Abrir no OsmAnd + Data de finalización + Data de inicio + Activar o monitoramento para recompilar datos de movemento no segundo plano. + Liña do tempo +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-he/strings.xml b/OsmAnd-telegram/res/values-he/strings.xml new file mode 100644 index 0000000000..292f6ef558 --- /dev/null +++ b/OsmAnd-telegram/res/values-he/strings.xml @@ -0,0 +1,192 @@ + +עדכון אחרון מטלגרם + נא לבחור שם שלא השתמשת בו עדיין + %1$s נוסף. + הוספה + לא ניתן להוסיף מכשיר חדש + נא לתת שם באורך של עד 200 תווים למכשיר החדש שלך. + שם המכשיר ארוך מדי + שם המכשיר לא יכול להישאר ריק + שם המכשיר + הסתרה + ניתן ליצור ולצפות במזהה ההתקן בלקוח הטלגרם הזה באמצעות רובוט ההתכתבות %1$s.‏ %2$s + אם ברצונך לחבר מגוון מכשירים לחשבון טלגרם אחד, עליך להשתמש במכשיר אחר כדי לשתף את המיקום שלך. + המיקום האחרון שעודכן: + נשלח ועודכן בהצלחה + אין אפשרות לשלוח להתכתבויות בטלגרם: + שליחת מיקום בתור + נא לבחור כיצד הודעות עם המיקום שלך תיראנה. + מפה + טקסט + מפה וטקסט + בהמתנה לתגובה מטלגרם + המיקום נשלח + מופעל + מתבצע איתור המיקום… + מתבצעת התחברות לאינטרנט + ניתן לשנות הגדרות מיטוב סוללה כדי לייצב את שיתוף המיקום. + עבודת רקע + מתבצע שיתוף ברקע + מעבר להגדרות + אחר כך + לא נשלח עדיין + לא נמצא עדיין + שליחת המיקום מחדש + המיקום האחרון הזמין + מצב שיתוף + שיתוף: פעיל + מצב + אין חיבור ל־GPS + אין חיבור לאינטרנט + השבתה + שמירה + הוספת מכשיר + שיתוף מיקום בתור + אנשי קשר וקבוצות שמשתפים אתך מיקום. + כיבוי מיטוב סוללה עבור טלגרם OsmAnd כדי שהשירות לא יכבה ברקע. + לצאת מטלגרם OsmAnd כדי לכבות את האפשרות לשתף מיקום או לצפות במיקום של אחרים\? + לצאת מטלגרם OsmAnd\? + שם + לפי מרחק + לפי שם + לפי קבוצה + מיון + מיון לפי + יציאה + תגובה אחרונה + קבוצה + יש להתחבר לאינטרנט כדי לצאת מטלגרם כראוי. + סגירה + נא לבחור גרסת OsmAnd שבה אנשי קשר יופיעו על המסך. + נא לבחור גרסת OsmAnd לשימוש + כיבוי שיתוף מיקום לכל ההתכתבויות הנבחרות (%1$d). + השבתת כל השיתופים + לכבות הכול + חשבון + הניטור פעיל + הניטור מושבת + זמן בתנועה + גובה ממוצע + מהירות ממוצעת + פתיחה ב־OsmAnd + מועד סיום + מועד התחלה + יש להפעיל ניטור כדי לאסוף נתוני תנועה ברקע. + כדי לשלול גישה לשיתוף המיקום. יש לפתוח את טלגרם, לגשת להגדרות ← פרטיות ← הפעלות ולסגור את הפעלת הטלגרם של OsmAnd. + איך לכבות את שיתוף המיקום של OsmAnd מטלגרם + איך לכבות את שיתוף המיקום של OsmAnd מטלגרם + חשבון מחובר + בעוד %1$s + נא לבחור את גרסת ה־OsmAnd בה יעשה שימוש OsmAnd טלגרם לטובת הצגת מיקומים. + להסתיר אנשי קשר שלא זזו במשך זה מסוים. + היסטוריית מיקום + הפעם האחרונה שאיש קשר זז. + אין תזוזה + שליחת המיקום שלי + מיקום + זמן השיתוף + תפוגה + השיתוף פעיל (כיבוי) + כיבוי שיתוף מיקום + פתיחת OsmAnd + חי + רובוט + הרשמה בטלגרם + כדי להשתמש בשיתוף מיקום צריך חשבון בטלגרם. + נא להתקין טלגרם ולהגדיר חשבון. + לאחר מכן תהיה לך אפשרות להשתמש ביישומון הזה. + הכול + כבוי + צריך חשבון רשום בטלגרם ומספר טלפון + אין לי חשבון בטלגרם + נא להקליד מספר טלפון + נא להקליד קוד אימות + הגדרת זמן חשיפה לכולם + %1$d שע׳ %2$d דק׳ + %1$d דק׳ + %1$d שע׳ + התקנה + שיתוף + חזרה + זמן חשיפה לכולם + נא להגדיר את משך הזמן שבו הקבוצות ואנשי הקשר הנבחרים שלך יראו את המיקום שלך בזמן אמת. + הגדרת זמן + נא לבחור את אנשי הקשר והקבוצות עמם ברצונך לשתף את מיקומך. + חיפוש: קבוצה או איש קשר + שיתוף מיקום + הצגה במפה + OsmAnd טלגרם + מספר טלפון + מספר טלפון בתצורה בינלאומית + ססמה + נא להכניס קוד + קוד אימות + נשלח אליך קוד מטלגרם עבור OsmAnd לטובת כניסה לחשבון שלך. + נא להקליד ססמה + ססמת טלגרם + כניסה + יציאה + התחלה + מתבצעת יציאה + מתבצעת סגירה + להפעיל „מיקום”\? + לא נכנסת + המשך + ביטול + הגדרות + ליישומון חסרה הרשאה לגישה לנתוני מיקום. + נא להפעיל את „מיקום” בהגדרות המערכת + נא לבחור את אחד מספקי המיקום כדי לשתף את המיקום שלך. + מצב רקע + OsmAnd טלגרם פועל ברקע בזמן שהמסך כבוי. + מרחק + שיתוף מיקום + המיקום משותף + שירות OsmAnd טלגרם + הלוגו של OsmAnd + עליך להתקין את הגרסה החופשית או את הגרסה בתשלום של OsmAnd תחילה + התקנת OsmAnd + הצגת משתמשים במפה + התכתבויות פעילות + אימות + נא להקליד את מספר הטלפון שלך בטלגרם בתצורה בינלאומית + ברוך בואך + יארד + רגל + מייל + ק״מ + מ׳ + מייל ימי + דק׳/מ׳ + דק׳/ק״מ + קשר + מ/שנ׳ + קמ״ש + מייל לשעה + קילומטרים לשעה + מיילים לשעה + מטרים לשנייה + דקות לקילומטר + דקות למייל + מייל ימי לשעה (קשר) + מיילים/רגל + מיילים/יארדים + קילומטרים/מטרים + מיילים ימיים + מיילים/מטרים + שע + דק׳ + שנ׳ + המיקום שלי + ציר זמן + הגדרת טווח הזמן המזערי לשיתוף מיקום. + שיתוף המקום דרךOsmAnd מאפשר לך לשתף את המיקום ולראות את מיקומם של אחרים ב־OsmAnd.

היישומון משתמש ב־API ודורש חשבון טלגרם.
+ חי כעת + נשלחו (%1$d ממתינות) + %1$d נקודות + תאריך + נאספו + נקודות GPS + נשלחו + OsmAnd connect +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-it/strings.xml b/OsmAnd-telegram/res/values-it/strings.xml new file mode 100644 index 0000000000..dfa1dd3667 --- /dev/null +++ b/OsmAnd-telegram/res/values-it/strings.xml @@ -0,0 +1,190 @@ + +Ultimo aggiornamento da Telegram + Scegli un nome che non hai ancora usato + %1$s aggiunto. + Aggiungi + Impossibile aggiungere il nuovo dispositivo + Dai un nome di non più di 200 caratteri al tuo dispositivo. + Nome del dispositivo troppo lungo + Il nome del dispositivo non può essere vuoto + Nome del dispositivo + Nascondi + Puoi creare e visualizzare l\'ID del dispositivo nel client Telegram usando il chat bot %1$s. %2$s + Se vuoi connettere più dispositivi a un account telegram, devi usare un dispositivo diverso per condividere la tua posizione. + Ultimo aggiornamento della posizione: + Inviato e caricato con successo + Impossibile da inviare alle chat Telegram: + Aspettando una risposta da Telegram + Inviando la posizione + Avvio + Localizzazione… + Collegamento a Internet + Cambia le impostazioni di ottimizzazione della batteria per stabilizzare la condivisione della posizione. + Funzionamento in secondo piano + Disattiva l\'ottimizzazione della batteria per OsmAnd Telegram in modo che non venga chiuso improvvisamente quando è in secondo piano. + Condivisione in secondo piano + Vai alle impostazioni + Più tardi + Non ancora inviato + Non ancora trovato + Invia nuovamente la posizione + Ultima posizione disponibile + Condivisione dello stato + Condivisione: Attivata + Stato + Nessuna connessione GPS + Nessuna connessione ad Internet + Disattiva + Salva + Aggiungi dispositivo + Condividi posizione come + Contatti e gruppi che condividono la posizione con te. + Disconnettersi da OsmAnd Telegram\? + Nome + Per distanza + Per nome + Per gruppo + Ordina + Ordina per + Seleziona la versione di OsmAnd in cui i contatti saranno mostrati sulla mappa. + Seleziona la versione di OsmAnd da usare + Disattiva la condivisione della posizione per tutte le conversazioni selezionate (%1$d). + Disattiva tutte le condivisioni + Disattiva tutto + Esci + fa + Ultima risposta + Gruppo + Connettiti a Internet per uscire correttamente dall\'account di Telegram. + Chiudi + Per revocare l\'accesso alla condivisione della posizione, apri Telegram, vai a Impostazioni → Privacy e Sicurezza → Sessioni, e termina la sessione di OsmAnd Telegram. + Come disattivare la condivisione della posizione di OsmAnd da Telegram + Come disattivare la condivisione della posizione di OsmAnd da Telegram + Account connesso + Account + in %1$s + Scegli la versione di OsmAnd che OsmAnd Telegram userà per mostrare le posizioni. + Nascondi i contatti che non si sono spostati per un certo periodo di tempo. + Cronologia delle posizioni + L\'ultima volta in cui un contatto si è spostato. + Non in movimento + Imposta l\'intervallo di tempo minimo per condividere la posizione. + Invia la mia posizione + Posizione + Tempo di condivisione + Scade + La condivisione è attivata (disattiva) + Disattiva la condivisione della posizione + Apri OsmAnd + In diretta + Bot + Registrazione a Telegram + Hai bisogno di un account Telegram per usare la condivisione della posizione. + Si prega di installare Telegram e creare un account. + Allora potrai usare quest\'applicazione. + Tutti + Spento + Hai bisogno di un account di Telegram e un numero di telefono + Non ho un account di Telegram + Inserisci un numero di telefono + Inserisci il codice di autenticazione + %1$d h %2$d m + %1$d m + %1$d h + Installa + Condividi + Indietro + Imposta il tempo in cui i contatti e gruppi che hai selezionato vedranno la tua posizione in tempo reale. + Imposta il tempo + Seleziona i contatti e i gruppi con cui vuoi condividere la tua posizione. + Cerca: gruppo o contatto + Condividi la posizione + Mostra sulla mappa + OsmAnd Telegram + Numero di telefono + Numero di telefono nel formato internazionale + Password + Inserisci il codice + Codice di autenticazione + Telegram ti ha inviato un codice per OsmAnd per entrare nel tuo account. + Inserisci la password + Password di Telegram + Entra + Esci + Avvio + Disconnessione + Chiusura + Attivare la \"posizione\"\? + Non sei connesso + Continua + Annulla + Impostazioni + L\'applicazione non ha i permessi per accedere ai dati sulla posizione. + Si prega di attivare la \"posizione\" nelle impostazioni di sistema + Seleziona uno dei provider della posizione per condividerla. + In secondo piano + OsmAnd Telegram funziona in secondo piano quando lo schermo è spento. + Distanza + Condividi la posizione + Condivisione della posizione + Servizio di OsmAnd Telegram + Logo di OsmAnd + Devi installare prima la versione gratis o a pagamento di OsmAnd + Installa OsmAnd + Mostra gli utenti sulla mappa + Conversazioni attive + Autorizzazione + Si prega di inserire il numero di telefono di Telegram nel formato internazionale + Benvenuto/a + + yd + ft + mi + km + m + nmi + min/m + Chilometri all\'ora + Miglia all\'ora + Metri al secondo + Minuti al chilometro + Minuti al miglio + Miglia nautiche all\'ora (nodi) + Miglia/piedi + Miglia/iarde + Chilometri/metri + Miglia nautiche + Miglia/metri + h + m + s + OsmAnd Location Sharing ti permette di condividere la tua posizione e di vedere quella di altri in OsmAnd.

L\'applicazione usa le API di Telegram ed è necessario un account di Telegram.
+ La mia posizione + Ora in diretta + + +Sei sicuro di volerti disconnettere da OsmAnd Telegram, cosicché non potrai condividere la posizione o vedere quella di altri\? + Connessione OsmAnd + Imposta un orario visibile a tutti + Orario visibile a tutti + min/km + kn + m/s + km/h + mi/h + Monitoraggio abilitato + Monitoraggio disabilitato + tempo in movimento + Altitudine media + Velocità media + Apri in OsmAnd + Data di fine + Data d\'inizio + Abilita il monitoraggio per raccogliere i dati degli spostamenti in secondo piano. + Invia la localizzazione come + Scegli l\'aspetto dei messaggi con la tua localizzazione. + Mappa + Testo + Mappa e testo + Cronologia +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-nb-rNO/strings.xml b/OsmAnd-telegram/res/values-nb/strings.xml similarity index 60% rename from OsmAnd-telegram/res/values-nb-rNO/strings.xml rename to OsmAnd-telegram/res/values-nb/strings.xml index 0658a16265..ea559058e9 100644 --- a/OsmAnd-telegram/res/values-nb-rNO/strings.xml +++ b/OsmAnd-telegram/res/values-nb/strings.xml @@ -1,33 +1,24 @@ - -Endre batterioptimiseringsinnstillinger, for stabil plasseringsdeling + +Endre batterioptimiseringsinnstillinger for mer stabil posisjonsdeling. Bakgrunnsarbeid - For stabil deling av din plassering i bakgrunnen, anbefales det å skru av batterioptimalisering for OsmAnd Telegram. -\n -\nHvis optimaliseringer er påskrudd, kan systemet skru av programmet som kjører i bakgrunnen automatisk (når skjermen er låst/eller programmet er minimert). Dette skjer uten merknad, og opphever kringkasting av plassering. + Skru av batterioptimisering for OsmAnd Telegram slik at det ikke plutselig skrur seg av når det er i bakgrunnen. Deling i bakgrunnen Gå til innstillinger Senere Ikke sendt enda Ikke funnet enda - Send plassering på ny - Sist sendte plassering - Sist tilgjengelige plassering + Send posisjon på nytt + Sist tilgjengelige posisjon Delingsstatus Deling: Påslått Status Ingen GPS-tilkobling - Sendt og oppdatert - Kunne ikke sende sludring: Ingen internettilknytning Skru av - Lagre - Skriv inn din enhets-ID fra https://live.osmand.net/device/ID - Enhets-ID Legg til enhet - Dette valget lar deg dele enhetens plassering, eller plassering for egendefinert lagt til via API-enheter. - Del plassering som - Kontakter og grupper som deler sin plassering med deg. - Er du sikker på at du vil logge ut av OsmAnd Telegram\? Etter det, vil du ikke kunne sende din plassering, og se dine kontakters plassering på kartet i OsmAnd. + Del posisjon som + Kontakter og grupper som deler sin posisjon med deg. + Er du sikker på at du vil logge ut av OsmAnd Telegram slik at du ikke kan sende din posisjon, eller se andres\? Logg ut av OsmAnd Telegram\? Navn Etter distanse @@ -37,39 +28,38 @@ Sorter etter Velg OsmAnd-versjon der kontaktene skal vises på kartet. Velg OsmAnd versjon å bruke - Det vil skru av deling av din plassering til alle valgte sludringer (%1$d). + Det vil skru av posisjonsdeling til alle valgte sludringer (%1$d). Skru av all deling Skru av alle Avslutt siden Siste svar Gruppe - For å logge ut av din Telegram-konto på rett vis, trenger du Internett. + Koble til Internett for å logge ut av Telegram ordentlig. Lukk - For å blokekre Telegram fra din OsmAnd-konto må du åpne innstillingene i Telegram. Under Personvern og sikkerhet - Økter, må du sluttføre OsmAnd Telegram-økta. Etter det, vil OsmAnd Plasseringsdeling ikke lenger ha tilgang til kontoen din, og du vil ikke kunne bruke dette programmet til du logger inn igjen. - Hvordan koble fra OsmAnd plasseringsdeling fra Telegram-kontoen - Hvordan koble fra OsmAnd plasseringsdeling fra Telegram + For å tilbakekalle posisjonsdelingstilgang. Åpne Telegram, gå til Innstillinger - Personvern og sikkerhet - Økter, og sluttfør OsmAnd Telegram-økta. + Hvordan koble fra OsmAnd posisjonsdeling fra Telegram + Hvordan koble fra OsmAnd posisjonsdeling fra Telegram Tilkoblet konto Konto i %1$s - Velg OsmAnd-versjonen OsmAnd Telegram henvender seg til for å vise posisjonen på kartet. + Velg OsmAnd-versjonen OsmAnd Telegram bruker for å vise posisjoner på kartet. OsmAnd connect Skjul kontaktene som ikke har oppdatert sin plassering etter et gitt tidsintervall. - Plasseringshistorikk + Posisjonshistorikk Siste tidspunkt en kontakt sendte en plasseringsoppdatering. Urørlig plassering - Sett minimumsintervall for plasseringsdeling. - Send min plassering + Sett minimumsintervall for posisjonsdeling. + Send min posisjon GPS og plassering Delingstid Utløper - Deling er påslått (skru av) - Skru av plasseringsdeling + Skru av posisjonsdeling Åpne OsmAnd Sanntid Bot Registrering i Telegram - Du trenger en Telegram-konto for å bruke funksjonene i OsmAnd plasseringsdeling. + Du trenger en Telegram-konto for å bruke funksjonene i OsmAnd posisjonsdeling. Hvis du ønsker å fortsette, installer Telegram fra Google Play og registrer din konto. Etter oppretting av konto, kan du bruke dette programmet. Alle @@ -86,11 +76,11 @@ Del Tilbake Synlig tid for alle - Sett tiden dine valgte kontakter og grupper vil se din plassering i sanntid. + Sett tiden dine valgte kontakter og grupper vil se din posisjon i sanntid. Sett tid - Velg kontaktene og gruppene du ønsker å dele din plassering med. + Velg kontaktene og gruppene du ønsker å dele din posisjon med. Søk: Gruppe eller kontakt - Start plasseringsdeling + Del posisjon Vis på kartet OsmAnd Telegram Telefonnummer @@ -98,32 +88,23 @@ Passord Skriv inn kode Identitetsbekreftelseskode - Du har blitt tilsendt en kode av Telegram. Den vil bli brukt av OsmAnd for å logge inn på kontoen din. + Telegram har sendt deg en kode slik at OsmAnd kan logge inn på kontoen din. Skriv inn passord - Skriv inn passordet for din Telegram-konto for å fullføre identitetsbekreftelse + Skriv inn passordet for din Telegram-konto Logg inn Logg ut Du er ikke innlogget - Igangsetting - Logger ut - Lukker Fortsett Avbryt - Skru på plasseringstjeneste\? Innstillinger - Programmet mangler tilgang til plasseringsdata. - Skru på GPS i innstillingene - Plasseringsdelingstjenesten krever at en plasseringstilbyder er påslått. + Programmet mangler tilgang til posisjonsdata. + Skru på «Posisjon» i systeminnstillingene + Velg en av posisjonstilbyderne for å dele din posisjon. Bakgrunnsmodus OsmAnd Telegram kjører som nisse med skjermen av. Distanse - Del plassering - Deler plassering - Pauset - Ingen data - Pause - Start - Stopp + Del posisjon + Deler posisjon OsmAnd Telegram-tjeneste OsmAnd-logo Du må installere gratis- eller betalt versjon av OsmAnd først @@ -131,7 +112,7 @@ Vis brukere på kartet Aktive sludringer Identitetsbekreftelse - Skriv inn telefonnummeret for din Telegram-konto i internasjonalt format + Skriv inn ditt Telegram-telefonnummer i internasjonalt format Velkommen km @@ -143,19 +124,17 @@ t min sek - + - OsmAnd plasseringsdeling lar deg dele din plassering og se andres posisjon i OsmAnd.

Programmet belager seg på Telegram-API-et. For å bruke dette programmet må du ha en Telegram-konto.
- Min plassering - Sender nå + OsmAnd posisjonsdeling lar deg dele din posisjon og se andres posisjon i OsmAnd.

Programmet belager seg på Telegram-API-et. For å bruke dette programmet må du ha en Telegram-konto.
+ Min posisjon -Sist oppdaterte plassering: +Sist oppdaterte posisjon: Sendt og oppdatert Kan ikke sende til Telegram-sludringer: Venter på svar fra Telegram - Sender plasseringsmeldinger + Sender posisjon Starter - Søker etter GPS + Posisjonerer… Kobler til Internett yd fot @@ -174,4 +153,40 @@ Kilometer/meter Nautiske mil Engelske mil/meter -
+ Du har allerede en enhet med samme navn, velg noe annet + %1$s lagt til. + Legg til + Kunne ikke legge til ny enhet + Skriv inn et nytt navn for din nye enhet. Maksimal lengde 200 tegn. + For lang enhetsnavn + Enhetsnavn må velges + Enhetsnavn + Skjul + Du kan opprette og vise enhets-ID i Telegram-klienten ved bruk av %1$s-sludrebot-en. %2$s + Hvis du vil koble flere enheter til én Telegram-konto, må du bruke en annen enhet til å dele din posisjon. + Lagre + Deling er på (skru av) + Starter + Logger ut + Lukker + Skru på \"Posisjon\"\? + Nå direkte + + +Siste oppdatering fra Telegram + Send plassering som + Velg hvordan meldinger med din plassering skal se ut. + Kart + Tekst + Kart og tekst + Oppsyn er påskrudd + Oppsyn er avskrudd + tid i bevegelse + Gjennomsnittlig høyde + Gjennomsnittsfart + Åpne i OsmAnd + Sluttdato + Startdato + Skru på oppsyn for å samle bevegelsesdata i bakgrunnen. + Tidslinje +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-pl/strings.xml b/OsmAnd-telegram/res/values-pl/strings.xml index d4b0ce3108..a32ff8c97b 100644 --- a/OsmAnd-telegram/res/values-pl/strings.xml +++ b/OsmAnd-telegram/res/values-pl/strings.xml @@ -1,5 +1,5 @@ - -Zmień ustawienia optymalizacji baterii, aby zapewnić stabilną lokalizację + +Zmień ustawienia optymalizacji baterii, aby zapewnić stabilną lokalizację. Praca w tle Aby stabilnie udostępniać swoją pozycję w tle, zaleca się wyłączenie optymalizacji baterii dla Telegram OsmAnd. \n @@ -10,25 +10,19 @@ Jeszcze nie wysłane Jeszcze nie znaleziono Wyślij ponownie lokalizację - Ostatnia wysłana lokalizacja Ostatnia dostępna lokalizacja Status udostępniania Udostępnianie: Włączone Status Brak połączenia GPS - Pomyślnie wysłano i zaktualizowano - Nie można wysłać na czat: Brak połączenia z internetem Wyłącz Zapisz - Wprowadź swój identyfikator urządzenia, który można znaleźć w https://live.osmand.net/device/ID - Identyfikator urządzenia Dodaj urządzenie - Ta opcja umożliwia udostępnianie lokalizacji urządzenia lub lokalizacji niestandardowych dodawanych przez urządzenia interfejsu API. Udostępnij lokalizację jako Kontakty i grupy, które udostępniają Ci swoją lokalizację. - Czy na pewno chcesz się wylogować z OsmAnd Telegram\? Po tym czasie nie będziesz mógł wysłać swojej pozycji i zobaczyć lokalizacji swoich kontaktów na mapie w OsmAnd. - Wyloguj się z OsmAnd Telegram\? + Czy na pewno chcesz się wylogować z OsmAnd Telegram, aby nie udostępniać lokalizacji ani nie widzieć lokalizacji innych\? + Wylogować się z OsmAnd Telegram\? Nazwa Według odległości Według nazwy @@ -37,43 +31,42 @@ Sortuj według Wybierz wersję OsmAnd, w której kontakty będą wyświetlane na mapie. Wybierz używaną wersję OsmAnd - To wyłączy udostępnianie lokalizacji dla wszystkich wybranych czatów (%1$d). + Wyłącza udostępnianie lokalizacji dla wszystkich wybranych czatów (%1$d). Wyłącz udostępnianie Wyłącz wszystko Zakończ temu Ostatnia odpowiedź Grupa - Internet jest wymagany, by właściwie wylogować się z konta Telegramu. + Połącz się z internetem, by właściwie wylogować się z konta Telegramu. Zamknij - Jak rozłączyć Udostępnianie Lokalizacji OsmAnd z konta Telegramu - Jak rozłączyć Udostępnianie Lokalizacji OsmAnd z Telegramu + Jak wyłączyć Udostępnianie Lokalizacji OsmAnd z Telegramu + Jak wyłączyć Udostępnianie Lokalizacji OsmAnd z Telegramu Połączone konta Konto w %1$s - Wybierz wersję OsmAnd do której Telegram OsmAnd będzie się odnosił przy wyświetlaniu pozycji na mapie. + Wybierz wersję OsmAnd, którą Telegram OsmAnd będzie używał do wyświetlaniu pozycji. Połącz OsmAnd - Ukryj kontakty, które nie zaktualizowały swojej lokalizacji po określonym czasie. + Ukryj kontakty, które nie zostały przeniesione w określonym czasie. Historia lokalizacji - Ostatni raz, kiedy kontakt wysłał aktualizację do swojej lokalizacji. + Ostatni kontakt został przeniesiony. Wybierz minimalny odstęp dla udostępniania lokalizacji. Wyślij moją lokalizację - GPS i lokalizacja + Pozycja Czas udostępniania - Wygasa o - Udostępnianie jest włączone (wyłącz) + Wygasa Wyłącz udostępnianie lokalizacji Otwórz OsmAnd Na żywo Bot Rejestracja w Telegramie - Potrzebujesz konto w Telegramie, by używać Udostępniania Lokalizacji OsmAnd. - Jeśli chcesz kontynuować, prosimy zainstalować Telegram z Google Play i zarejestrować konto. - Po utworzeniu konta, możesz używać aplikacji. + Aby korzystać z udostępniania lokalizacji, potrzebujesz konta Telegram. + Zainstaluj Telegram i skonfiguruj konto. + Następnie możesz użyć tej aplikacji. Wszystkie Wyłączone - Potrzebujesz już zarejestrowanego konta i numeru telefonu w Telegramie - Nie masz konta w Telegramie + Potrzebujesz zarejestrowanego konta w Telegramie i numeru telefonu + Nie mam konta w Telegramie Wpisz numer telefonu Wpisz kod zabezpieczający Ustaw czas widoczny dla wszystkich @@ -87,8 +80,8 @@ Ustaw czas, kiedy wybrane kontakty i grupy będą widzieć twoją lokalizację w czasie rzeczywistym. Ustaw czas Wybierz kontakty i grupy, którym chcesz udostępnić swoją lokalizację. - Wyszukiwanie: grupa i kontakt - Rozpocznij udostępnianie lokalizacji + Szukaj: Grupa lub kontakt + Udostępnij lokalizację Pokazuj na mapie OsmAnd Telegram Numer telefonu @@ -96,32 +89,24 @@ Hasło Wpisz kod Kod zabezpieczający - Telegram wysłał ci kod. Będzie on używany przez OsmAnd jako login do konta. + Telegram wysłał ci kod do OsmAnd jako login do konta. Wpisz kod - Proszę wpisać kod konta w Telegramie by zakończyć autoryzację + Hasło Telegram Zaloguj się Wyloguj się Nie jesteś zalogowany/a - Inicjalizacja - Wylogowywanie - Zamykanie Kontynuuj Anuluj - Usługa lokalizacji nie jest włączona. Włączyć ją\? + Włączyć lokalizację\? Opcje Aplikacja nie ma uprawnień dostępu do danych o położeniu. - Proszę włączyć GPS w opcjach - Usługa udostępniania lokalizacji wymaga włączonego dostawcy lokalizacji. + Włącz \"Lokalizacja\" w ustawieniach systemu + Wybierz jednego z dostawców lokalizacji, aby udostępnić swoją lokalizację. Tryb tła Telegram OsmAnd będzie działał w tle przy wygaszonym ekranie. Odległość Udostępnij lokalizację Udostępnianie lokalizacji - Wstrzymano - Brak danych - Wstrzymaj - Rozpocznij - Zakończ Usługa Telegramu OsmAnd Logo OsmAnd Najpierw musisz zainstalować darmową lub płatną wersję OsmAnd @@ -129,7 +114,7 @@ Pokazuj użytkowników na mapie Aktywuj czat Autoryzacja - Proszę wprowadzić numer telefonu konta Telegramu w formacie międzynarodowym + Wprowadź swój numer Telegram w formacie międzynarodowym Witamy yd @@ -137,7 +122,7 @@ mi km m - nmi + Mm min/m min/km nmi/h @@ -158,10 +143,50 @@ h min sek - + - Udostępnianie Lokalizacji OsmAnd umożliwi ci udostępnianie twojej lokalizacji i zobaczenie lokalizacji innych osób w OsmAnd.

Aplikacja działa w opraciu o Telegram API. By używać aplikacji musisz mieć konto w Telegramie.
+ Udostępnianie Lokalizacji OsmAnd pozwala Ci udostępniać swoją lokalizację i widzieć ją w OsmAnd.

Aplikacja używa Telegram API i potrzebujesz konta Telegram.
Moja lokalizacja - Na żywo teraz + Na żywo -
+Wybierz nazwę, której jeszcze nie używasz + %1$s dodany. + Dodaj + Nie można dodać nowego urządzenia + Nazwij swoje nowe urządzenie maksymalnie 200 znaków. + Nazwa urządzenia jest za długa + Nazwa sprzętu nie można być pusta + Nazwa urządzenia + Ukryj + Możesz utworzyć i zobaczyć ID sprzętu w telegramie klienta, używając %1$s bota czatu. %2$s + Jeśli chcesz połączyć wiele urządzeń z jednym kontem telegramu, potrzebujesz użyć innego sprzętu do udostępniania lokalizacji. + Ostatnia aktualizacja lokalizacji: + Wysłano i zaktualizowano z powodzeniem + Nie można wysłać na czaty Telegramu: + Oczekiwanie na odpowiedź od Telegramu + Wysyłam lokalizację + Wystartuj + Pozycjonowanie… + Łączenie z internetem + Aby anulować dostęp do udostępniania lokalizacji. Otwórz Telegram, przejdź do Ustawienia → Prywatność i bezpieczeństwo → Sesje i zakończ sesję OsmAnd Telegram. + Nie porusza się + Ostatnia aktualizacja z Telegram + Udostępnianie jest włączone (wyłącz) + Wystartuj + Wylogowuję się + Zamykanie + Wyślij lokalizację jako + Wybierz sposób wyświetlania wiadomości z Twoją lokalizacją. + Mapa + Tekst + Mapa i tekst + Monitorowanie jest włączone + Monitorowanie jest wyłączone + czas w ruchu + Średnia wysokość + Średnia prędkość + Otwarte w OsmAnd + Data końcowa + Data rozpoczęcia + Umożliwia monitorowanie, by zgromadzić dane ruchu w tle. + Linia czasu +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-pt-rBR/strings.xml b/OsmAnd-telegram/res/values-pt-rBR/strings.xml new file mode 100644 index 0000000000..ae84e0fc3b --- /dev/null +++ b/OsmAnd-telegram/res/values-pt-rBR/strings.xml @@ -0,0 +1,193 @@ + +Enviar localização como + Escolha como as mensagens com sua localização serão exibidas. + Mapa + Texto + Mapa e texto + Última atualização do Telegram + Escolha um nome que você ainda não usou + %1$s adicionado. + Adicionar + Não foi possível adicionar novo dispositivo + Nomeie seu novo dispositivo no máximo 200 símbolos. + Nome do dispositivo muito longo + O nome do dispositivo não pode estar vazio + Nome do dispositivo + Ocultar + Você pode criar e visualizar o ID do dispositivo no cliente de telegrama usando o bot de bate-papo %1$s. %2$s + Se você deseja conectar vários dispositivos a uma conta de telegrama, é necessário usar um dispositivo diferente para compartilhar sua localização. + Última localização atualizada: + Enviado e atualizado com sucesso + Não é possível enviar para bate-papo do Telegram: + Aguardando resposta do Telegram + enviando localização + Iniciando + Posicionando… + Conectando-se à Internet + Altere as configurações de otimização da bateria para estabilizar o compartilhamento de local. + "Funcionamento em segundo plano " + Desative a otimização da bateria do OsmAnd Telegram para que não seja desligado repentinamente quando estiver em segundo plano. + Compartilhando em segundo plano + Vá para as configurações + Mais tarde + Ainda não enviado + Ainda não encontrado + Reenvie o local + Última localização disponível + Status de compartilhamento + Compartilhamento: Ativado + Status + Sem conexão GPS + Sem conexão com a internet + Desabilitar + Salvar + Adicionar dispositivo + Compartilhar localização como + Contatos e grupos compartilhando o local para você. + Tem certeza de que deseja sair do OsmAnd Telegram para que você não possa compartilhar a localização ou ver a localização de outras pessoas\? + Sair do OsmAnd Telegram\? + Nome + Pela distância + Por nome + Por grupo + Ordenar + Classificar por + Selecione a versão OsmAnd, onde os contatos serão exibidos no mapa. + Selecione a versão do OsmAnd para usar + Desativa o compartilhamento de local para todos os bate-papos selecionados (%1$d). + Desativar todo o compartilhamento + Desligue todos + Sair + atrás + Última resposta + Grupo + Conecte-se à Internet para efetuar a saída do Telegram corretamente. + Fechar + Para revogar o acesso ao compartilhamento de local. Abra o Telegram, vá para Configurações → Privacidade e Segurança → Sessões e termine a sessão do OsmAnd Telegram. + "Como desativar o compartilhamento de local do OsmAnd para Telegram" + "Como desativar o compartilhamento de local do OsmAnd para Telegram" + Conta conectada + Conta + no %1$s + Escolha a versão OsmAnd que OsmAnd Telegram usa para exibir posições. + "conectar ao OsmAnd " + Ocultar contatos que não foram movidos em um determinado momento. + Histórico de localização + A última vez que um contato foi movido. + Parado + Defina o intervalo mínimo para o compartilhamento de local. + Enviar minha localização + Posição + Tempo de compartilhamento + Expira + O compartilhamento está ativado (desativado) + Desativar o compartilhamento de local + Abrir OsmAnd + Vivo + Robô + Registro no Telegram + Você precisa de uma conta do Telegram para usar o compartilhamento de local. + Por favor, instale o Telegram e configure uma conta. + Então você pode usar este aplicativo. + Todos + Desativado + Você precisa de uma conta e número de telefone registrados no Telegram + Eu não tenho conta Telegram + Digite o número de telefone + Digite o código de autenticação + Definir tempo visível para todos + %1$d h %2$d m + %1$d m + %1$d h + Instalar + Compartilhar + Voltar + Hora visível para todos + Defina a hora em que seus contatos e grupos selecionados verão sua localização em tempo real. + Definir tempo + Selecione os contatos e grupos com os quais você deseja compartilhar sua localização. + Pesquisa: Grupo ou contato + Compartilhar localização + Mostrar no mapa + OsmAnd Telegram + Número de telefone + Número de telefone no formato internacional + Senha + Coloque o código + Código de Autenticação + Uma faixa GPX é salva automaticamente durante a navegação. + Digite a senha + Senha do telegrama + Entrar + Sair + Iniciando + Saindo + Fechando + Ativar \"Localização\"\? + você não está logado no + Continuar + Cancelar + Configurações + O aplicativo não tem permissão para acessar os dados de localização. + Por favor, ligue \"Localização\" nas configurações do sistema + Selecione um dos provedores de localização para compartilhar sua localização. + Modo em segundo plano + OsmAnd Telegram é executado em segundo plano com a tela desligada. + Distância + Compartilhar localização + Compartilhando localização + Serviço OsmAnd Telegram + Logotipo do OsmAnd + Você precisa instalar a versão gratuita ou paga do OsmAnd primeiro + Instalar OsmAnd + Mostrar usuários no mapa + Bate-papos ativos + Autorização + Por favor, insira o número de telefone do seu Telegram em formato internacional + Boas vindas + yd + ft + mi + km + m + mn + min/m + min/km + mn/h + m/s + km/h + mph + Quilômetros por hora + Milhas por hora + Metros por segundo + Minutos por quilômetro + Minutos por milha + Milhas náuticas por hora (nó) + Milhas/pés + Milhas/jardas + Quilômetros/metros + Milhas náuticas + Milhas/metros + h + min + seg + Compartilhamento de local OsmAnd permite que você compartilhe sua localização e veja a dos outros no OsmAnd.

O aplicativo usa a API Telegram e você precisa de uma conta Telegram.
+ Minha localização + Ao vivo agora + Monitoramento está ativado + Monitoramento está desativado + tempo em movimento + Altitude média + Velocidade média + Aberto no OsmAnd + Data final + Data de início + Ative o monitoramento para coletar dados de movimento em segundo plano. + "Linha do tempo " + enviado (%1$d em buffer) + "%1$d pontos " + Data + Coletado + Pontos de Gps + "Enviar " +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-ru/strings.xml b/OsmAnd-telegram/res/values-ru/strings.xml index a182df7976..7896e335c8 100644 --- a/OsmAnd-telegram/res/values-ru/strings.xml +++ b/OsmAnd-telegram/res/values-ru/strings.xml @@ -1,19 +1,143 @@ -Не отправлено + + Включите мониторинг, для сбора данных о перемещении в фоновом режиме. + Последнее обновление в Telegram + Имя устройства + Спрятать + Вы можете создать и просмотреть идентификатор устройства в клиенте telegram, используя %1$s чат бота. %2$s + Подключитесь к Интернету, чтобы правильно выйти из Telegram. + Установить видимое время для всех + %1$d ч %2$d м + %1$d м + %1$d ч + Вы не вошли в систему + Трансляция местоположения + Показывать пользователей на карте + Активные чаты + Отправка местоположения + Запуск + Трансляция в фоновом режиме + Приложение не имеет разрешения на доступ к данным о местоположении. + Пожалуйста, включите «Местоположение» в системных настройках + Выберите один из провайдеров определения местоположения, чтобы поделиться своим местоположением. + Работа в фоне + Отключите оптимизацию батареи для OsmAnd Telegram, чтобы оно не было внезапно отключено в фоновом режиме. + Фоновый режим + OsmAnd Telegram работает в фоновом режиме с выключенным экраном. + Выберите версию OsmAnd, в которой контакты будут отображаться на карте. + Выберите версию OsmAnd для использования + Остановить трансляцию своей позиции всем ранее выбранным чатам (%1$d). + Отключить все трансляции + Активных + Бот + Закрыть + Инициализация + Выберите время + Выберите имя, которое вы еще не использовали + %1$s добавлен. + Не удалось добавить новое устройство + Имя устройства слишком длинное + Имя устройства не может быть пустым + Пароль + Введите код + Код аутентификации + Telegram отправил вам код для того, чтобы OsmAnd вошел в вашу учетную запись. + Введите пароль + Пароль Telegram + OsmAnd Location Sharing позволяет вам делиться своим местоположением и видеть других в OsmAnd.

Приложение использует Telegram API, и вам нужна учетная запись Telegram.]]>
+ Номер телефона + Номер телефона в международном формате + Авторизация + Введите номер телефона Telegram в международном формате + Добро пожаловать + Регистрация в Telegram + Для использования отправки местоположений вам нужна учетная запись Telegram. + Пожалуйста, установите Telegram и настройте учетную запись. + Затем вы можете использовать это приложение. + Вам нужна зарегистрированная учетная запись Telegram и номер телефона + У меня нет учетной записи Telegram + Введите номер телефона + Введите код аутентификации + Добавить + Назовите новое устройство макс. 200 символов. + Расстояние + Моя локация + Активно сейчас + ч + мин + сек + Имя + По расстоянию + По имени + По группе + Сортировать + Сортировать по + Отстановить все + Выход + назад + Последний ответ + Группа + Контакты и группы, отправляющие вам местоположение. + Поделиться местоположением + Установить + Установить OsmAnd + Продолжить + Отмена + Выберите контакты и группы, с которыми вы хотите поделиться своим местоположением. + Не отправлено + Показать на карте + Открыть OsmAnd + Все + Отключить + Настройки + Время трансляции + Отключить трансляцию + Истекает в + через %1$s + Поделиться + Назад + Установите время, в течение которого выбранные вами контакты и группы будут видеть ваше местоположение в режиме реального времени. + Видимое время для всех + Последнее обновление локации: + Успешно отправлено и обновлено + Невозможно отправить Telegram чаты: + Ожидание ответа от Telegram + Поиск GPS + Подключение к Интернету + Измените настройки оптимизации батареи, чтобы обеспечить стабильную отправку местоположения + История местоположений + В последний раз контакт отправил местоположение. + Устаревание местоположения + Установите минимальный интервал между отправками местоположения. + Отправка моей локации + GPS & местоположение + Скройте контакты, которые не обновили свое местоположение за определенный промежуток времени. + Поделиться местоположением как + Если вы хотите подключить несколько устройств к одной учетной записи Telegram, вам необходимо использовать другое устройство для трансляции местоположения. + Выберите версию OsmAnd которую OsmAnd Telegram использует для отображения позиций на карте. + OsmAnd подключение + Связанная учетная запись + Учетная запись + Начать отправку локации + Вам необходимо сначала установить бесплатную или платную версию OsmAnd + Как отключить передачу данных OsmAnd от Telegram + Как отключить передачу данных OsmAnd от Telegram + Отмена доступа к отправке локаций. Откройте Telegram, перейдите в Настройки - Конфиденциальность и безопасность - Сессии и завершите сеанс OsmAnd Telegram. + Выйти из OsmAnd Telegram? + Войти + Выйти + Вы уверены, что хотите выйти из OsmAnd Telegram, так вы не сможете делиться местоположением или не видеть местоположения других? + Не найдено Отправить местоположение - Последнее отправление Последнее местоположение Статус отправки - Отправка: включена + Трансляция: включена Статус Отсутствует GPS - Отправлено успешно - Нет возможности отправить в: Отсутствует интернет Отключить Сохранить - ID устройства Добавить устройство Перейти в настройки Позже diff --git a/OsmAnd-telegram/res/values-sc/strings.xml b/OsmAnd-telegram/res/values-sc/strings.xml new file mode 100644 index 0000000000..39edfd5169 --- /dev/null +++ b/OsmAnd-telegram/res/values-sc/strings.xml @@ -0,0 +1,193 @@ + +Artària mèdia + Lestresa mèdia + Aberi in OsmAnd + Data de fine + Data de incumintzu + Mapa + Testu + Mapa e testu + Annanghe + Nùmene de su dispositivu + Istichi + Prus a tardu + Istadu + Disabìlita + Sarva + Nùmene + Òrdina + Essi + Serra + Totus + Istudadu + Installa + Cumpartzi + In dae segus + Sighi + Annulla + Impostatziones + S’aplicatzione no est autorizada a atzèdere a sos datos de positzione. + Allughe sa \"positzione\" in sas impostatziones + Modalidade de isfundu + OsmAnd Telegram sighit a funtzionare in s’isfundu, cun s’ischermu mortu. + Distàntzia + Cumpartzi sa positzione + Installa OsmAnd + Tzarradas ativas + Su monitoràgiu est abilitadu + Su monitoràgiu est disabilitadu + tempus in movimentu + Abìlita su monitoràgiu pro collire datos de su movimentu in s\'isfundu. + Imbia sa positzione comente + Issèbera comente sos messàgios cun sa positzione tua ant a aparire. + Ùrtimu agiornamentu de Telegram + Issèbera unu nùmene chi no as giai impreadu + %1$s annantu. + Impossìbile annànghere su dispositivu nou + Dae unu nùmene de non prus de 200 caràteres a su dispositivu nou tuo. + Nùmene de su dispositivu tropu longu + Su nùmene de su dispositivu non podet èssere bòidu + Podes creare e abbaidare s\'ID de su dispositivu in su cliente de Telegram impreende su bot pro sas tzarradas %1$s. %2$s + Si cheres cunnètere prus dispositivos a unu contu de Telegram, depes impreare unu dispositivu diferente pro cumpartzire sa positzione tua. + Ùrtima positzione agiornada: + Imbiadu e agiornadu cun sutzessu + Impossìbile imbiare a sas tzarradas de Telegram: + Isetende una risposta dae Telegram + Imbiende sa positzione + Incumintzende + Positzionende… + Collegamentu a ìnternet + Muda sas impostatziones de otimizatzione de sa bateria pro istabilizare sa cumpartzidura de sa positzione. + Modalidade de isfundu + Istuda s\'otimizatzione de sa bateria pro OsmAnd Telegram pro fàghere in modu chi non bèngiat serradu de repente cando est in s\'isfundu. + Cumpartzidura in s\'isfundu + Bae a sas impostatziones + Galu non imbiadu + Galu no agatadu + Torra a imbiare sa positzione + Ùrtima positzione disponìbile + Istadu de cumpartzidura + Cumpartzidura: Abilitada + Peruna connessione GPS + Peruna connessione a ìnternet + Annanghe unu dispositivu + Cumpartzi sa positzione comente + Cuntatos e grupos chi cumpartzent sa positzione cun tie. + Ses seguru de chèrrere essire dae OsmAnd Telegram pro non cumpartzire sa positzione tua o bìdere sa de àtere\? + Serrare sa sessione de OsmAnd Telegram\? + Pro distàntzia + Pro nùmene + Pro grupu + Òrdina pro + Ischerta sa versione de OsmAnd in ue sos cuntatos ant a èssere ammustrados in sa mapa. + Ischerta sa versione de OsmAnd de impreare + Istuda sa cumpartzidura de sa positzione pro totu sas tzarradas ischertadas (%1$d). + Istuda totu sas cumpartziduras + Istuda totu + a como + Ùrtima risposta + Grupu + Collega·ti a ìnternet pro essire in manera curreta dae Telegram. + Pro bogare s\'atzessu a sa cumpartzidura de sa positzione. Aberi Telegram, bae a Cunfiguratziones → Riservadesa e Seguresa → Sessiones ativas, e acaba sa sessione de OsmAnd Telegram. + Comente istudare sa cumpartzidura de sa positzione de OsmAnd dae Telegram + Comente istudare sa cumpartzidura de sa positzione de OsmAnd dae Telegram + Contu connessu + Contu + in %1$s + Issèbera sa versione de OsmAnd chi OsmAnd Telegram at a impreare pro ammustrare sas positziones. + Istichi sos cuntatos chi non si sunt mòvidos in unu perìodu de tempus determinadu. + Cronologia de sas positziones + S\'ùrtima borta chi unu cuntatu s\'est mòvidu. + Non in movimentu + Imposta s\'intervallu mìnimu pro cumpartzire sa positzione. + Imbia sa positzione mea + Positzione + Tempus de cumpartzidura + Iscadit a sas + Sa cumpartzidura est alluta (istuda) + Istuda sa cumpartzidura de sa positzione + Aberi OsmAnd + In direta + Bot + Registratzione in Telegram + Tenes bisòngiu de unu contu de Telegram pro impreare sa cumpartzidura de sa positzione. + Pro praghere installa Telegram e imposta unu contu. + A pustis as a pòdere impreare s\'aplicatzione. + Tenes bisòngiu de unu contu registradu de Telegram e de unu nùmeru de telèfono + Non tèngio unu contu de Telegram + Inserta unu nùmeru de telèfono + Inserta su còdighe de autenticatzione + Imposta un\'oràriu visìbile pro totus + %1$d h %2$d m + %1$d m + %1$d h + Oràriu visìbile pro totus + yd + ft + mi + km + m + nmi + min/m + min/km + nmi/h + m/s + km/h + mph + Chilòmetros a s\'ora + Mìglios a s\'ora + Metros a su segundu + Minutos a su chilòmetru + Minutos a su mìgliu + Mìglios nàuticos a s\'ora (knot) + Mìglios/pedes + Mìglios/iardas + Chilòmetros/metros + Mìglios nàuticos + Mìglios/metros + h + min + seg + Positzione mea + Lìnia temporale + Connessione OsmAnd + Imposta su tempus in su cale sos cuntatos e sos grupos chi as ischertadu ant a bìdere sa positzione tua in direta. + Imposta su tempus + Ischerta sos cuntatos e sos grupos cun sos cales cheres cumpartzire sa positzione. + Chirca: grupu o cuntatu + Cumpartzi sa positzione + Ammustra in sa mapa + OsmAnd Telegram + Nùmeru de telèfono + Nùmeru de telèfono in formadu internatzionale + Crae de intrada + Inserta su còdighe + Còdighe de autenticatzione + Telegram t\'at imbiadu unu còdighe pro permìtere a OsmAnd de intrare in su contu tuo. + Inserta sa crae + Crae de Telegram + Intra + Essi + Incumintzende + Essende + Serrende + Allùghere sa \"Positzione\"\? + No as fatu s\'atzessu + Ischerta unu de sos frunidores de sa positzione pro la cumpartzire. + Cumpartzende sa positzione + Servìtziu de OsmAnd Telegram + Logo de OsmAnd + In antis depes installare sa versione de badas o a pagamentu de OsmAnd + Ammustra sos impreadores in sa mapa + Autorizatzione + Pro praghere inserta su nùmeru de telèfono tuo in formadu internatzionale + Bene bènnidu + Sa Cumpartzidura de sa Positzione de OsmAnd ti permitit de cumpartzire sa positzione tua e de bìdere cussa de sos àteros in OsmAnd.

S\'aplicatzione impreat s\'API de Telegram e tenes bisòngiu de unu contu de Telegram.
+ In direta como + imbiadu (%1$d in sa memòria tampone) + %1$d puntos + Data + Collidos + Puntos GPS + Imbiadu +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values-sl/strings.xml b/OsmAnd-telegram/res/values-sl/strings.xml new file mode 100644 index 0000000000..980ec22611 --- /dev/null +++ b/OsmAnd-telegram/res/values-sl/strings.xml @@ -0,0 +1,93 @@ + +Izberite ime, ki ga še niste uporabili + Naprava %1$s je uspešno dodana. + Dodaj + Ni mogoče dodati nove naprave + Ime nove naprave je omejeno z 200 znaki. + Ime naprave je predolgo + Ime naprave mora biti dodeljeno + Ime naprave + Skrij + ID naprave je mogoče ustvariti in pogledati v odjemalcu Telegram z uporabo klepetalnega robota %1$s. %2$s + Nazadnje zabeleženo mesto: + Podatki so uspešno poslani in posodobljeni. + Ni mogoče pošiljati klepetov Telegram: + Poteka čakanje na odziv programa Telegram + Pošiljanje podatkov o trenutnem mestu + Začenjanje + Poteka določevanje mesta … + Poteka vzpostavljanje povezave na internet + Skoči na nastavitve + Kasneje + Ni še poslano + Ni še najdeno + Ponovno pošiljanje trenutnega mesta + Zadnje določeno mesto + Objavljanje stanja + Objavljanje: omogočeno + Stanje + Ni povezave z GPS + Ni vzpostavljene internetne povezave + Onemogoči + Shrani + Dodaj napravo + Objavi trenutno mesto kot + Ali se želite odjaviti iz storitve OsmAnd Telegram\? + Ime + Po razdalji + Po imenu + Po skupini + Razvrsti + Razvrsti po + Izbor različice OsmAnd za uporabo + Onemogoči vse objavljanje + Izklopi vse + Končaj + Zadnji odziv + Skupina + Pred odjavo storitve Telegram je treba vzpostaviti internetno povezavo. + Zapri + Povezan račun + Račun + v %1$s + Pokaži na zemljevidu + OsmAnd Telegram + Telefonska številka + Telefonska številka v mednarodnem zapisu + Geslo + Koda + Overitvena koda + Namesti program OsmAnd + Pokaži uporabnike na zemljevidu + Dejavni klepeti + Overitev + Telefonsko številko programa Telegram je treba vpisati v mednarodnem zapisu + Dobrodošli + + yd + ft + mi + km + m + nmi + min/m + min/km + nmi/h + m/s + km/h + mph + Kilometri na uro + Milje na uro + Metri na sekundo + Minute na kilometer + Minute na miljo + Navtične milje na uro (vozli) + Milje/Čevlji + Milje/Jardi + Kilometri/Metri + Navtične milje + Milje/Metri + h + min + sek + diff --git a/OsmAnd-telegram/res/values-uk/strings.xml b/OsmAnd-telegram/res/values-uk/strings.xml new file mode 100644 index 0000000000..91c5164ce2 --- /dev/null +++ b/OsmAnd-telegram/res/values-uk/strings.xml @@ -0,0 +1,163 @@ + +Оберіть назву, яку ви ще не використовували + %1$s додано. + Додати + Не вдалося додати новий пристрій + Назвіть новий пристрій, максимум 200 символів. + Назва пристрою занадто довге + Назва пристрою не може бути порожнім + Назва пристрою + Приховати + Ви можете створити та переглянути ідентифікатор пристрою в клієнті telegram, використовуючи %1$s чат боту. %2$s + Якщо бажаєте під\'єднати декілька пристроїв до одного облікового запису Telegram, Вам необхідно використовувати інший пристрій для трансляції позиції. + Крайнє оновлення позиціювання: + Успішно надіслано та оновлено + Неможливо надіслати чати Telegram: + Очікування відповіді від Telegram + Надсилання позиціювання + Запуск + Позиціювання… + Зʼєднання із інтернетом + Змініть налаштування оптимізації батареї, для стабільної трансляції позиціювання. + Робота у фоні + Вимкніть оптимізацію батареї для OsmAnd Telegram, аби додаток не вимикався у фоновому режимі. + Трансляція у фоновому режимі + Перейти до налаштувань + Пізніше + Ще не надіслано + Ще не знайдено + Повторно надіслати позицію + Крайнє доступне позиціювання + Стан трансляції + Трансляція: Увімкнута + Статус + Немає зʼєднання із GPS + Немає зʼєднання із інтернетом + Вимкнути + Зберегти + Додати пристрій + Поділитися позицією як + Контакти та групи, які транслюють свою позицію Вам. + Ви впевнені, що бажаєте вийти з OsmAnd Telegram\? Оскільки не зможете транслювати свою позицію чи бачити позицію інших. + Вийти з OsmAnd Telegram\? + Імʼя + За відстанню + За імʼям + За групою + Впорядкувати + Впорядкувати за + Оберіть версію OsmAnd, в якій будуть показуватись контакти. + Оберіть версію OsmAnd для використання + Вимкнути трансляцію позиції усім обраним чатам (%1$d). + Вимкнути всі трансляції + Вимкнути все + Вихід + тому + Остання відповідь + Група + Зʼєднайтеся із інтернетом, аби коректно вийти з Telegram. + Закрити + Скасування дозволу до трансляції позиції. Відкрийте Telegram, перейдіть до пункту Налаштування → Конфіденційність та безпека → Активні сесії, та завершіть сеанс OsmAnd Telegram. + Як вимкнути OsmAnd трансляцію позиції з Telegram + Як вимкнути OsmAnd трансляцію позиції з Telegram + Повʼязаний обліковий запис + Обліковий запис + через %1$s + Оберіть версію OsmAnd, яку OsmAnd Telegram буде використовувати для показу позицій на карті. + Підключення OsmAnd + Приховайте контакти, які не рухалися на протязі визначеного проміжку часу. + Історія позицій + Надіслати мою позицію + Позиція + Час трансляцій + Трансляція активна (вимкнути) + Вимкнути трансляцію + Відкрити OsmAnd + Бот + Реєстрація в Telegram + Необхідно мати обліковий запис Telegram для трансляції позиціювання. + Будь ласка, встановіть Telegram та налаштуйте обліковий запис. + Тоді Ви зможете використовувати цей додаток. + Всі + Вимкнути + Вам необхідно мати зареєстрований обліковий запис Telegram та номер телефону + Я не маю облікового запису Telegram + Введіть номер телефону + Введіть код аутентифікації + %1$d г %2$d х + %1$d х + %1$d г + Встановити + Поділитися + Назад + Оберіть час + Оберіть контакти та групи, яким ви транслюватимете свою позицію. + Пошук: Група або контакт + Транслювати позицію + Показати на карті + OsmAnd Telegram + Номер телефону + Номер телефону у міжнародному форматі + Пароль + Введіть код + Код аутентифікації + Telegram надіслав Вам код, аби OsmAnd увійшов до вашого облікового запису. + Введіть пароль + Пароль Telegram + Увійти + Вийти + Запуск + Вихід із системи + Закриття + Увімкнути «Позиціювання»\? + Ви не увійшли до системи + Продовжити + Скасувати + Налаштування + Додаток не має дозволу до отримання даних позиціювання. + Будь ласка, увімкніть «Позиціювання» у системних налаштуваннях + Фоновий режим + OsmAnd працює у фоновому режимі з вимкненим екраном. + Відстань + Поділитися позицією + Трансляція позиції + Вам спершу необхідно встановити безкоштовну, чи платну версію OsmAnd + Встановити OsmAnd + Показувати користувачів на карті + Активні чати + Авторизація + Будь ласка, введіть Ваш Telegram номер телефону у міжнародному форматі + Ласкаво просимо + + ярд + фут + мл + км + м + мор.м. + хв/м + хв/км + вузл + м/с + км/г + мл/г + Кілометрів за годину + Миль на годину + Метрів за секунду + Хвилин за кілометр + Хвилин за милю + Морських миль на годину (вузол) + Милі, фути + Милі, ярди + Кілометри, метри + Морські милі + Милі, метри + г + хв + сек + OsmAnd Location Sharing дозволяє Вам транслювати свою позицію та бачити позицію інших в OsmAnd.

Додаток використовує Telegram API та потребує облікового запису Telegram.
+ Моя позиція + Встановіть мінімальний інтервал між надсиланням позиції. + Діє до + Нещодавнє оновлення з Telegram +
diff --git a/OsmAnd-telegram/res/values-zh-rTW/strings.xml b/OsmAnd-telegram/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000000..b2ee1c5c00 --- /dev/null +++ b/OsmAnd-telegram/res/values-zh-rTW/strings.xml @@ -0,0 +1,188 @@ + +傳送位置為 + 選擇您所在位置怎樣的訊息外觀。 + 地圖 + 文字 + 地圖與文字 + %1$s 已新增。 + 新增 + 無法增加新的裝置 + 命名您的新裝置,最多 200 個字符。 + 裝置名稱太長 + 裝置名稱不能為空白 + 裝置名稱 + 隱藏 + 上次更新的位置: + 正在傳送位置 + 正在啟動 + 正在定位… + 正在連線至網路 + 背景工作 + 在背景分享 + 轉至設定 + 隨後 + 尚未傳送 + 尚未找到 + 重新傳送位置 + 最後可用的位置 + 分享狀態 + 分享:已啟用 + 狀態 + 沒有 GPS 連接 + 沒有網際網路連線 + 停用 + 儲存 + 新增裝置 + 分享位置為 + 名稱 + 按距離 + 按名稱 + 按群組 + 分類 + 排序方式 + 停用所有分享 + 關閉全部 + 離開 + Telegram 的最後一次更新 + 選擇一個尚未使用的名稱 + 您可以使用 %1$s 聊天機器人在電報用戶端中創建和查看裝置 ID。 %2$s + 如果要將多個設備連接到一個 Telegram 帳戶,則需要使用不同的設備來共用您的位置。 + 已成功發送和更新 + 無法發送到 Telegram 聊天: + 等待 Telegram 回復 + 更改電池最佳化設置以穩定位置共用。 + 關閉 OsmAnd Telegram 的電池最佳化,這樣在後臺時就不會突然關閉。 + 連絡人和群組共用位置給您。 + 你確定要登出 OsmAnd Telegram,這樣你就不能共用位置或看到其他人的位置嗎? + 要登出 OsmAnd Telegram 嗎\? + 選擇將在地圖上顯示連絡人的 OsmAnd 版本。 + 選擇要使用的 OsmAnd 版本 + 關閉所有選定聊天的位置共用(%1$d)。 + + 上次回應 + 群組 + 連接到網路以正確登出 Telegram。 + 關閉 + 撤銷位置共用訪問。打開 Telegram,進入設定→隱私和安全→會話,並終 OsmAnd Telegram 會話。 + 如何關閉 Telegram 中的 OsmAnd 交給位置共用 + 如何關閉 Telegram 中的 OsmAnd 交給位置共用 + 已連接帳戶 + 帳戶 + 在 %1$s + 選擇 OsmAnd 和版本的 OsmAnd Telegram 用於顯示位置。 + OsMand 連接 + 隱藏在給定時間內未移動的連絡人。 + 位置歷史記錄 + 上次移動連絡人的時間。 + 沒有移動 +\n + 設置位置共用的最小間隔。 + 發送我的位置 + 位置 + 共用時間 + 到期 + 共用已打開 (關閉) + 關閉位置共用 + 打開 OsmAnd + 即時 + 提供 + 註冊 Telegram + 您需要一個 Telegram 帳戶才能使用位置共用。 + 請安裝 Telegram 並建立帳戶。 + 然後您可以使用這個應用程式。 + 所有 + 關閉 + 你需要一個 Telegram 帳號和電話號碼 + 我沒有 Telegram 帳戶 + 添加電話號碼 + 輸入身份驗證程式碼 + 為所有人設定可見時間 + %1$d 時 %2$d 分 + %1$d 分 + %1$d 時 + 安裝 + 分享 + 返回 + 所有人的可見時間 + 設置所選連絡人和組即時查看您的位置的時間。 + 設定時間 + 選擇要共用位置的連絡人和群組。 + 搜尋:群組或連絡人 + 共用位置 + 在地圖上顯示 + OsmAnd Telegram + 電話號碼 + 國際格式的電話號碼 + 密碼 + 輸入代碼 + 身份驗證代碼 + Telegram 給你發了一個代碼,要求 OsmAnd 登入你的帳戶。 + 輸入密碼 + Telegram 密碼 + 登入 + 登出 + 正在啟動 + 已登出 + 關閉 + 打開“位置”? + 您沒有登入 + 繼續 + 取消 + 設定 + 應用程式沒有存取位置數據的許可權。 + 請在系統設置中打開“位置” + 選擇一個位置提供商來共享您的位置。 + 背景模式 + 當螢幕關閉後,讓 OsmAnd 在背景運行。 + 距離 + 共用位置 + 共用位置 + OsmAnd Telegram 伺服器 + OsmAnd 圖示 + 您需要先安裝免費或付費版本的 OsmAnd + 安裝 OsMand + 在地圖上顯示用戶 + 主動聊天 + 授權 + 請以國際格式輸入 Telegram 電話號碼 + 歡迎 + + + + 公里 + 公尺 + + 分/公尺 + 分/公里 + 浬/時 + 公尺/秒 + 公里/小時 + 哩/小時 + 公里每小時 + 英里每小時 + 公尺每秒 + 分鐘每公里 + 分鐘每英里 + 浬每小時 (節) + 英哩/英呎 + 英哩/碼 + 公里/公尺 + 海浬 + 英里/米 + + + + OsmAnd 位置共用可以讓您共用您的位置,並在 OsmAnd 中看到其他人的位置。

該應用程式使用電報 API,你需要一個 Telegram 帳戶。
+ 我的位置 + 即時狀況 + 監視已啟用 + 監視已停用 + 移動時間 + 平均海拔 + 平均速率 + 在 OsmAnd 中開啟 + 結束日期 + 開始日期 + 啟用監視以在背景蒐集移動資料。 + 時間軸 +
\ No newline at end of file diff --git a/OsmAnd-telegram/res/values/dimens.xml b/OsmAnd-telegram/res/values/dimens.xml index 6b2139f3a5..7bd8b000db 100644 --- a/OsmAnd-telegram/res/values/dimens.xml +++ b/OsmAnd-telegram/res/values/dimens.xml @@ -52,6 +52,9 @@ 32dp 60dp + 56dp + 50dp + 132dp 30dp diff --git a/OsmAnd-telegram/res/values/strings.xml b/OsmAnd-telegram/res/values/strings.xml index 8b3555f5f1..d93be06214 100644 --- a/OsmAnd-telegram/res/values/strings.xml +++ b/OsmAnd-telegram/res/values/strings.xml @@ -1,13 +1,47 @@ + Please update OsmAnd to view data on the map + Update + sent (%1$d in buffer) + %1$d points + Date + Collected + Gps points + Sent + Monitoring is enabled + Monitoring is disabled + time on the move + Average altitude + Average speed + Open in OsmAnd + End date + Start date + Enable monitoring to collect movement data in the background. + Send location as + Choose how messages with your location will look like. + Map + Text + Map and text + Last update from Telegram + Pick a name you haven\'t already used + %1$s added. + Add + Could not add new device + Name your new device in max. 200 symbols. + Device name too long + Device name cannot be empty + Device name + Hide + You can create and view the Device ID in the telegram client using the %1$s chat bot. %2$s + If you want to connect multiple devices to one telegram account, you need to use different Device to share your location. Last updated location: Successfully sent and updated Not possible to send to Telegram chats: Waiting for response from Telegram - Sending location messages - Initializing - Searching for GPS - Connecting to the internet - Change battery optimization settings, for stable location sharing + Sending location + Starting + Positioning… + Connecting to the Internet + Change battery optimization settings to stabilize location sharing. Background work Turn off battery optimization for OsmAnd Telegram so that it isn\'t suddenly turned off when in the background. Sharing in the background @@ -16,21 +50,15 @@ Not sent yet Not found yet Re-send location - Last sent location Last available location Sharing status Sharing: Enabled Status No GPS connection - Sent and updated - Could not send to chats: No internet connection Disable Save - Find your device ID at https://live.osmand.net/device/ID - Device ID Add device - Allow sharing your device location, or location of custom devices added via API. Share location as Contacts and groups sharing location to you. Are you sure you want to log out of OsmAnd Telegram so you can\'t share location or see the location of others? @@ -52,32 +80,33 @@ Group Connect to the Internet to properly log out of Telegram. Close - To revoke location sharing access. Open Telegram, go to Settings - Privacy and Security - Sessions and terminate the OsmAnd Telegram session. + To revoke location sharing access. Open Telegram, go to Settings → Privacy and Security → Sessions, and terminate the OsmAnd Telegram session. How to turn off OsmAnd Location Sharing from Telegram How to turn off OsmAnd Location Sharing from Telegram Connected account Account in %1$s - Choose the OsmAnd version OsmAnd Telegram uses to display positions on the map. + Choose the OsmAnd version OsmAnd Telegram uses to display positions. OsmAnd connect - Hide the contacts that have not updated their location in a given amount of time. + Hide contacts that have not moved in a given time. Location history - The last time a contact sent a location. - Stale location + The last time a contact moved. + Not moving Set the minimum interval for location sharing. Send my location - GPS & location + Position Sharing time - Expires at - Sharing is enabled (disable) + + Expires + Sharing is on (turn off) Turn off location sharing Open OsmAnd Live Bot Registration in Telegram - You need a Telegram account to use OsmAnd Location Sharing. - Please install Telegram from Google Play and register an account to do so. - After creating the account, you can use this app. + You need a Telegram account to use location sharing. + Please install Telegram and set up an account. + Then you can use this app. All Off You need a registered Telegram account and phone number @@ -94,42 +123,37 @@ Visible time for all Set the time that your selected contacts and groups will see your location in real time. Set time - Select the contacts and groups with whom you want to share your location. - Search: group or contact - Start location sharing + Select the contacts and groups you want to share your location with. + Search: Group or contact + Share location Show on map OsmAnd Telegram Phone number - Phone number in the international format + Phone number in international format Password Enter code Authentication code Telegram has sent you a code for OsmAnd to log in on your account. Enter password - Please enter your Telegram account password + Telegram password Log in Log out - You are not logged in - Initialization + Starting Logging out Closing + Turn on \"Location\"? + You are not logged in Continue Cancel - Turn on location service? Settings App lacks permission for location data access. - Please turn on GPS in the settings - Select on of the location providers to share your location. + Please turn on \"Location\" in the system settings + Select one of the location providers to share your location. Background mode OsmAnd Telegram runs in the background with the screen off. Distance Share location Sharing location - Paused - No data - Pause - Start - Stop OsmAnd Telegram service OsmAnd logo You need to install the free or paid version of OsmAnd first @@ -166,10 +190,10 @@ h min sec - + OsmAnd Location Sharing lets you share your location and see that of others in the OsmAnd.

The app uses Telegram API and you need a Telegram account.]]>
My location - Sending now + Live now + Timeline
diff --git a/OsmAnd-telegram/src/net/osmand/PlatformUtil.java b/OsmAnd-telegram/src/net/osmand/PlatformUtil.java index 9ec83421ae..f1d8143948 100644 --- a/OsmAnd-telegram/src/net/osmand/PlatformUtil.java +++ b/OsmAnd-telegram/src/net/osmand/PlatformUtil.java @@ -1,6 +1,11 @@ package net.osmand; +import android.util.Xml; + import org.apache.commons.logging.Log; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; public class PlatformUtil { @@ -143,4 +148,11 @@ public class PlatformUtil { return getLog(cl.getName()); } + public static XmlPullParser newXMLPullParser() throws XmlPullParserException { + return Xml.newPullParser(); + } + + public static XmlSerializer newSerializer() { + return Xml.newSerializer(); + } } diff --git a/OsmAnd-telegram/src/net/osmand/aidl/IOsmAndAidlCallback.aidl b/OsmAnd-telegram/src/net/osmand/aidl/IOsmAndAidlCallback.aidl index be010bb062..6f0cb8f65b 100644 --- a/OsmAnd-telegram/src/net/osmand/aidl/IOsmAndAidlCallback.aidl +++ b/OsmAnd-telegram/src/net/osmand/aidl/IOsmAndAidlCallback.aidl @@ -1,9 +1,14 @@ package net.osmand.aidl; import net.osmand.aidl.search.SearchResult; +import net.osmand.aidl.gpx.AGpxBitmap; interface IOsmAndAidlCallback { void onSearchComplete(in List resultSet); void onUpdate(); + + void onAppInitialized(); + + void onGpxBitmapCreated(in AGpxBitmap bitmap); } \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/aidl/IOsmAndAidlInterface.aidl b/OsmAnd-telegram/src/net/osmand/aidl/IOsmAndAidlInterface.aidl index d7ebb4492d..59fd213ab6 100644 --- a/OsmAnd-telegram/src/net/osmand/aidl/IOsmAndAidlInterface.aidl +++ b/OsmAnd-telegram/src/net/osmand/aidl/IOsmAndAidlInterface.aidl @@ -55,6 +55,9 @@ import net.osmand.aidl.maplayer.point.ShowMapPointParams; import net.osmand.aidl.navdrawer.SetNavDrawerItemsParams; +import net.osmand.aidl.navdrawer.NavDrawerFooterParams; +import net.osmand.aidl.navdrawer.NavDrawerHeaderParams; + import net.osmand.aidl.navigation.PauseNavigationParams; import net.osmand.aidl.navigation.ResumeNavigationParams; import net.osmand.aidl.navigation.StopNavigationParams; @@ -67,6 +70,16 @@ import net.osmand.aidl.search.SearchResult; import net.osmand.aidl.search.SearchParams; import net.osmand.aidl.navigation.NavigateSearchParams; +import net.osmand.aidl.customization.SetWidgetsParams; +import net.osmand.aidl.customization.OsmandSettingsParams; + +import net.osmand.aidl.gpx.AGpxFile; +import net.osmand.aidl.gpx.AGpxFileDetails; +import net.osmand.aidl.gpx.CreateGpxBitmapParams; +import net.osmand.aidl.tiles.ASqliteDbFile; + +import net.osmand.aidl.plugins.PluginParams; + // NOTE: Add new methods at the end of file!!! interface IOsmAndAidlInterface { @@ -133,4 +146,34 @@ interface IOsmAndAidlInterface { long registerForUpdates(in long updateTimeMS, IOsmAndAidlCallback callback); boolean unregisterFromUpdates(in long callbackId); + + boolean setNavDrawerLogo(in String imageUri); + + boolean setEnabledIds(in List ids); + boolean setDisabledIds(in List ids); + boolean setEnabledPatterns(in List patterns); + boolean setDisabledPatterns(in List patterns); + + boolean regWidgetVisibility(in SetWidgetsParams params); + boolean regWidgetAvailability(in SetWidgetsParams params); + + boolean customizeOsmandSettings(in OsmandSettingsParams params); + + boolean getImportedGpx(out List files); + + boolean getSqliteDbFiles(out List files); + boolean getActiveSqliteDbFiles(out List files); + boolean showSqliteDbFile(String fileName); + boolean hideSqliteDbFile(String fileName); + + boolean setNavDrawerLogoWithParams(in NavDrawerHeaderParams params); + boolean setNavDrawerFooterWithParams(in NavDrawerFooterParams params); + + boolean restoreOsmand(); + + boolean changePluginState(in PluginParams params); + + boolean registerForOsmandInitListener(in IOsmAndAidlCallback callback); + + boolean getBitmapForGpx(in CreateGpxBitmapParams file, IOsmAndAidlCallback callback); } \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/aidl/customization/OsmandSettingsParams.aidl b/OsmAnd-telegram/src/net/osmand/aidl/customization/OsmandSettingsParams.aidl new file mode 100644 index 0000000000..770070bb46 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/customization/OsmandSettingsParams.aidl @@ -0,0 +1,3 @@ +package net.osmand.aidl.customization; + +parcelable OsmandSettingsParams; \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/aidl/customization/OsmandSettingsParams.java b/OsmAnd-telegram/src/net/osmand/aidl/customization/OsmandSettingsParams.java new file mode 100644 index 0000000000..bff8c68018 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/customization/OsmandSettingsParams.java @@ -0,0 +1,60 @@ +package net.osmand.aidl.customization; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class OsmandSettingsParams implements Parcelable { + + private String sharedPreferencesName; + private Bundle bundle; + + public OsmandSettingsParams(@NonNull String sharedPreferencesName, @Nullable Bundle bundle) { + this.sharedPreferencesName = sharedPreferencesName; + this.bundle = bundle; + } + + public OsmandSettingsParams(Parcel in) { + readFromParcel(in); + } + + public static final Creator CREATOR = new Creator() { + @Override + public OsmandSettingsParams createFromParcel(Parcel in) { + return new OsmandSettingsParams(in); + } + + @Override + public OsmandSettingsParams[] newArray(int size) { + return new OsmandSettingsParams[size]; + } + }; + + public String getSharedPreferencesName() { + return sharedPreferencesName; + } + + public Bundle getBundle() { + return bundle; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(sharedPreferencesName); + out.writeBundle(bundle); + } + + @SuppressLint("ParcelClassLoader") + private void readFromParcel(Parcel in) { + sharedPreferencesName = in.readString(); + bundle = in.readBundle(); + } + + @Override + public int describeContents() { + return 0; + } +} diff --git a/OsmAnd-telegram/src/net/osmand/aidl/customization/SetWidgetsParams.aidl b/OsmAnd-telegram/src/net/osmand/aidl/customization/SetWidgetsParams.aidl new file mode 100644 index 0000000000..235a4abe51 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/customization/SetWidgetsParams.aidl @@ -0,0 +1,3 @@ +package net.osmand.aidl.customization; + +parcelable SetWidgetsParams; \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/aidl/customization/SetWidgetsParams.java b/OsmAnd-telegram/src/net/osmand/aidl/customization/SetWidgetsParams.java new file mode 100644 index 0000000000..d9343b920e --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/customization/SetWidgetsParams.java @@ -0,0 +1,93 @@ +package net.osmand.aidl.customization; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; + +import java.util.ArrayList; +import java.util.List; + +public class SetWidgetsParams implements Parcelable { + + private String widgetKey; + private List appModesKeys; + + public SetWidgetsParams(String widgetKey, @Nullable List appModesKeys) { + this.widgetKey = widgetKey; + this.appModesKeys = appModesKeys; + } + + public SetWidgetsParams(Parcel in) { + readFromParcel(in); + } + + public static final Creator CREATOR = new Creator() { + @Override + public SetWidgetsParams createFromParcel(Parcel in) { + return new SetWidgetsParams(in); + } + + @Override + public SetWidgetsParams[] newArray(int size) { + return new SetWidgetsParams[size]; + } + }; + + public String getWidgetKey() { + return widgetKey; + } + + public List getAppModesKeys() { + return appModesKeys; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(widgetKey); + writeStringList(out, appModesKeys); + } + + private void readFromParcel(Parcel in) { + widgetKey = in.readString(); + appModesKeys = readStringList(in); + } + + @Override + public int describeContents() { + return 0; + } + + private void writeStringList(Parcel out, List val) { + if (val == null) { + out.writeInt(-1); + return; + } + int N = val.size(); + int i = 0; + out.writeInt(N); + while (i < N) { + out.writeString(val.get(i)); + i++; + } + } + + private List readStringList(Parcel in) { + List list = new ArrayList<>(); + int M = list.size(); + int N = in.readInt(); + if (N == -1) { + return null; + } + int i = 0; + for (; i < M && i < N; i++) { + list.set(i, in.readString()); + } + for (; i < N; i++) { + list.add(in.readString()); + } + for (; i < M; i++) { + list.remove(N); + } + return list; + } +} diff --git a/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxBitmap.aidl b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxBitmap.aidl new file mode 100644 index 0000000000..128f5e6b94 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxBitmap.aidl @@ -0,0 +1,4 @@ +package net.osmand.aidl.gpx; + +parcelable AGpxBitmap; + diff --git a/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxBitmap.java b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxBitmap.java new file mode 100644 index 0000000000..2f423e126e --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxBitmap.java @@ -0,0 +1,50 @@ +package net.osmand.aidl.gpx; + +import android.graphics.Bitmap; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +public class AGpxBitmap implements Parcelable { + + private Bitmap bitmap; + + public AGpxBitmap(@NonNull Bitmap bitmap) { + this.bitmap = bitmap; + } + + public AGpxBitmap(Parcel in) { + readFromParcel(in); + } + + public Bitmap getBitmap() { + return bitmap; + } + + public void setBitmap(Bitmap bitmap) { + this.bitmap = bitmap; + } + + public static final Creator CREATOR = new + Creator() { + public AGpxBitmap createFromParcel(Parcel in) { + return new AGpxBitmap(in); + } + + public AGpxBitmap[] newArray(int size) { + return new AGpxBitmap[size]; + } + }; + + public void writeToParcel(Parcel out, int flags) { + out.writeParcelable(bitmap, flags); + } + + private void readFromParcel(Parcel in) { + bitmap = in.readParcelable(Bitmap.class.getClassLoader()); + } + + public int describeContents() { + return 0; + } +} diff --git a/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFile.aidl b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFile.aidl new file mode 100644 index 0000000000..413d34a571 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFile.aidl @@ -0,0 +1,3 @@ +package net.osmand.aidl.gpx; + +parcelable AGpxFile; \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFile.java b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFile.java new file mode 100644 index 0000000000..9572057fdc --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFile.java @@ -0,0 +1,89 @@ +package net.osmand.aidl.gpx; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class AGpxFile implements Parcelable { + + private String fileName; + private long modifiedTime; + private long fileSize; + private boolean active; + private AGpxFileDetails details; + + public AGpxFile(@NonNull String fileName, long modifiedTime, long fileSize, boolean active, @Nullable AGpxFileDetails details) { + this.fileName = fileName; + this.modifiedTime = modifiedTime; + this.fileSize = fileSize; + this.active = active; + this.details = details; + } + + public AGpxFile(Parcel in) { + readFromParcel(in); + } + + public static final Creator CREATOR = new + Creator() { + public AGpxFile createFromParcel(Parcel in) { + return new AGpxFile(in); + } + + public AGpxFile[] newArray(int size) { + return new AGpxFile[size]; + } + }; + + public String getFileName() { + return fileName; + } + + public long getModifiedTime() { + return modifiedTime; + } + + public long getFileSize() { + return fileSize; + } + + public boolean isActive() { + return active; + } + + public AGpxFileDetails getDetails() { + return details; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeString(fileName); + out.writeLong(modifiedTime); + out.writeLong(fileSize); + out.writeByte((byte) (active ? 1 : 0)); + + out.writeByte((byte) (details != null ? 1 : 0)); + if (details != null) { + out.writeParcelable(details, flags); + } + } + + private void readFromParcel(Parcel in) { + fileName = in.readString(); + modifiedTime = in.readLong(); + fileSize = in.readLong(); + active = in.readByte() != 0; + + boolean hasDetails= in.readByte() != 0; + if (hasDetails) { + details = in.readParcelable(AGpxFileDetails.class.getClassLoader()); + } else { + details = null; + } + } + + public int describeContents() { + return 0; + } +} + diff --git a/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFileDetails.aidl b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFileDetails.aidl new file mode 100644 index 0000000000..1e2cdec2b1 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFileDetails.aidl @@ -0,0 +1,3 @@ +package net.osmand.aidl.gpx; + +parcelable AGpxFileDetails; \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFileDetails.java b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFileDetails.java new file mode 100644 index 0000000000..3fe755135e --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/gpx/AGpxFileDetails.java @@ -0,0 +1,196 @@ +package net.osmand.aidl.gpx; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class AGpxFileDetails implements Parcelable { + + private float totalDistance = 0; + private int totalTracks = 0; + private long startTime = Long.MAX_VALUE; + private long endTime = Long.MIN_VALUE; + private long timeSpan = 0; + private long timeMoving = 0; + private float totalDistanceMoving = 0; + + private double diffElevationUp = 0; + private double diffElevationDown = 0; + private double avgElevation = 0; + private double minElevation = 99999; + private double maxElevation = -100; + + private float minSpeed = Float.MAX_VALUE; + private float maxSpeed = 0; + private float avgSpeed; + + private int points; + private int wptPoints = 0; + + private List wptCategoryNames = new ArrayList<>(); + + public AGpxFileDetails(float totalDistance, int totalTracks, + long startTime, long endTime, + long timeSpan, long timeMoving, float totalDistanceMoving, + double diffElevationUp, double diffElevationDown, + double avgElevation, double minElevation, double maxElevation, + float minSpeed, float maxSpeed, float avgSpeed, + int points, int wptPoints, Set wptCategoryNames) { + this.totalDistance = totalDistance; + this.totalTracks = totalTracks; + this.startTime = startTime; + this.endTime = endTime; + this.timeSpan = timeSpan; + this.timeMoving = timeMoving; + this.totalDistanceMoving = totalDistanceMoving; + this.diffElevationUp = diffElevationUp; + this.diffElevationDown = diffElevationDown; + this.avgElevation = avgElevation; + this.minElevation = minElevation; + this.maxElevation = maxElevation; + this.minSpeed = minSpeed; + this.maxSpeed = maxSpeed; + this.avgSpeed = avgSpeed; + this.points = points; + this.wptPoints = wptPoints; + if (wptCategoryNames != null) { + this.wptCategoryNames = new ArrayList<>(wptCategoryNames); + } + } + + public AGpxFileDetails(Parcel in) { + readFromParcel(in); + } + + public static final Creator CREATOR = new + Creator() { + public AGpxFileDetails createFromParcel(Parcel in) { + return new AGpxFileDetails(in); + } + + public AGpxFileDetails[] newArray(int size) { + return new AGpxFileDetails[size]; + } + }; + + public float getTotalDistance() { + return totalDistance; + } + + public int getTotalTracks() { + return totalTracks; + } + + public long getStartTime() { + return startTime; + } + + public long getEndTime() { + return endTime; + } + + public long getTimeSpan() { + return timeSpan; + } + + public long getTimeMoving() { + return timeMoving; + } + + public float getTotalDistanceMoving() { + return totalDistanceMoving; + } + + public double getDiffElevationUp() { + return diffElevationUp; + } + + public double getDiffElevationDown() { + return diffElevationDown; + } + + public double getAvgElevation() { + return avgElevation; + } + + public double getMinElevation() { + return minElevation; + } + + public double getMaxElevation() { + return maxElevation; + } + + public float getMinSpeed() { + return minSpeed; + } + + public float getMaxSpeed() { + return maxSpeed; + } + + public float getAvgSpeed() { + return avgSpeed; + } + + public int getPoints() { + return points; + } + + public int getWptPoints() { + return wptPoints; + } + + public List getWptCategoryNames() { + return wptCategoryNames; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeFloat(totalDistance); + out.writeInt(totalTracks); + out.writeLong(startTime); + out.writeLong(endTime); + out.writeLong(timeSpan); + out.writeLong(timeMoving); + out.writeFloat(totalDistanceMoving); + out.writeDouble(diffElevationUp); + out.writeDouble(diffElevationDown); + out.writeDouble(avgElevation); + out.writeDouble(minElevation); + out.writeDouble(maxElevation); + out.writeFloat(minSpeed); + out.writeFloat(maxSpeed); + out.writeFloat(avgSpeed); + out.writeInt(points); + out.writeInt(wptPoints); + out.writeStringList(wptCategoryNames); + } + + private void readFromParcel(Parcel in) { + totalDistance = in.readFloat(); + totalTracks = in.readInt(); + startTime = in.readLong(); + endTime = in.readLong(); + timeSpan = in.readLong(); + timeMoving = in.readLong(); + totalDistanceMoving = in.readFloat(); + diffElevationUp = in.readDouble(); + diffElevationDown = in.readDouble(); + avgElevation = in.readDouble(); + minElevation = in.readDouble(); + maxElevation = in.readDouble(); + minSpeed = in.readFloat(); + maxSpeed = in.readFloat(); + avgSpeed = in.readFloat(); + points = in.readInt(); + wptPoints = in.readInt(); + in.readStringList(wptCategoryNames); + } + + public int describeContents() { + return 0; + } +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/aidl/gpx/CreateGpxBitmapParams.aidl b/OsmAnd-telegram/src/net/osmand/aidl/gpx/CreateGpxBitmapParams.aidl new file mode 100644 index 0000000000..b02d5c6e70 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/gpx/CreateGpxBitmapParams.aidl @@ -0,0 +1,3 @@ +package net.osmand.aidl.gpx; + +parcelable CreateGpxBitmapParams; \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/aidl/gpx/CreateGpxBitmapParams.java b/OsmAnd-telegram/src/net/osmand/aidl/gpx/CreateGpxBitmapParams.java new file mode 100644 index 0000000000..b6af1d354c --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/gpx/CreateGpxBitmapParams.java @@ -0,0 +1,101 @@ +package net.osmand.aidl.gpx; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.File; + +public class CreateGpxBitmapParams implements Parcelable { + + private File gpxFile; + private Uri gpxUri; + private float density; + private int widthPixels; + private int heightPixels; + private int color; //ARGB color int + + public CreateGpxBitmapParams(File gpxFile, float density, int widthPixels, int heightPixels, int color) { + this.gpxFile = gpxFile; + this.density = density; + this.widthPixels = widthPixels; + this.heightPixels = heightPixels; + this.color = color; + } + + public CreateGpxBitmapParams(Uri gpxUri, float density, int widthPixels, int heightPixels, int color) { + this.gpxUri = gpxUri; + this.density = density; + this.widthPixels = widthPixels; + this.heightPixels = heightPixels; + this.color = color; + } + + public CreateGpxBitmapParams(Parcel in) { + readFromParcel(in); + } + + public static final Creator CREATOR = new + Creator() { + public CreateGpxBitmapParams createFromParcel(Parcel in) { + return new CreateGpxBitmapParams(in); + } + + public CreateGpxBitmapParams[] newArray(int size) { + return new CreateGpxBitmapParams[size]; + } + }; + + public File getGpxFile() { + return gpxFile; + } + + public Uri getGpxUri() { + return gpxUri; + } + + public int getWidthPixels() { + return widthPixels; + } + + public int getHeightPixels() { + return heightPixels; + } + + public float getDensity() { + return density; + } + + public int getColor() { + return color; + } + + public void writeToParcel(Parcel out, int flags) { + if (gpxFile != null) { + out.writeString(gpxFile.getAbsolutePath()); + } else { + out.writeString(null); + } + out.writeParcelable(gpxUri, flags); + out.writeFloat(density); + out.writeInt(widthPixels); + out.writeInt(heightPixels); + out.writeInt(color); + } + + private void readFromParcel(Parcel in) { + String gpxAbsolutePath = in.readString(); + if (gpxAbsolutePath != null) { + gpxFile = new File(gpxAbsolutePath); + } + gpxUri = in.readParcelable(Uri.class.getClassLoader()); + density = in.readFloat(); + widthPixels = in.readInt(); + heightPixels = in.readInt(); + color = in.readInt(); + } + + public int describeContents() { + return 0; + } +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerFooterParams.aidl b/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerFooterParams.aidl new file mode 100644 index 0000000000..fc1271a8ca --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerFooterParams.aidl @@ -0,0 +1,3 @@ +package net.osmand.aidl.navdrawer; + +parcelable NavDrawerFooterParams; diff --git a/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerFooterParams.java b/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerFooterParams.java new file mode 100644 index 0000000000..9bfd70193f --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerFooterParams.java @@ -0,0 +1,68 @@ +package net.osmand.aidl.navdrawer; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class NavDrawerFooterParams implements Parcelable { + + @NonNull + private String packageName; + @Nullable + private String intent; + @Nullable + private String appName; + + @NonNull + public String getPackageName() { + return packageName; + } + + @Nullable + public String getIntent() { + return intent; + } + + @Nullable + public String getAppName() { + return appName; + } + + public NavDrawerFooterParams(@NonNull String packageName, @Nullable String intent, + @Nullable String appName) { + this.packageName = packageName; + this.intent = intent; + this.appName = appName; + } + + protected NavDrawerFooterParams(Parcel in) { + packageName = in.readString(); + intent = in.readString(); + appName = in.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(packageName); + dest.writeString(intent); + dest.writeString(appName); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public NavDrawerFooterParams createFromParcel(Parcel in) { + return new NavDrawerFooterParams(in); + } + + @Override + public NavDrawerFooterParams[] newArray(int size) { + return new NavDrawerFooterParams[size]; + } + }; +} diff --git a/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerHeaderParams.aidl b/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerHeaderParams.aidl new file mode 100644 index 0000000000..c230824841 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerHeaderParams.aidl @@ -0,0 +1,3 @@ +package net.osmand.aidl.navdrawer; + +parcelable NavDrawerHeaderParams; diff --git a/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerHeaderParams.java b/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerHeaderParams.java new file mode 100644 index 0000000000..c88950fd10 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/navdrawer/NavDrawerHeaderParams.java @@ -0,0 +1,68 @@ +package net.osmand.aidl.navdrawer; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +public class NavDrawerHeaderParams implements Parcelable { + + @NonNull + private String imageUri; + @NonNull + private String packageName; + @Nullable + private String intent; + + @NonNull + public String getImageUri() { + return imageUri; + } + + @NonNull + public String getPackageName() { + return packageName; + } + + @Nullable + public String getIntent() { + return intent; + } + + public NavDrawerHeaderParams(@NonNull String imageUri, @NonNull String packageName, + @Nullable String intent) { + this.imageUri = imageUri; + this.packageName = packageName; + this.intent = intent; + } + + public NavDrawerHeaderParams(Parcel in) { + imageUri = in.readString(); + packageName = in.readString(); + intent = in.readString(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(imageUri); + dest.writeString(packageName); + dest.writeString(intent); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public NavDrawerHeaderParams createFromParcel(Parcel in) { + return new NavDrawerHeaderParams(in); + } + + @Override + public NavDrawerHeaderParams[] newArray(int size) { + return new NavDrawerHeaderParams[size]; + } + }; +} diff --git a/OsmAnd-telegram/src/net/osmand/aidl/plugins/PluginParams.aidl b/OsmAnd-telegram/src/net/osmand/aidl/plugins/PluginParams.aidl new file mode 100644 index 0000000000..beff693f5a --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/plugins/PluginParams.aidl @@ -0,0 +1,5 @@ +// PluginParams.aidl +package net.osmand.aidl.plugins; + +parcelable PluginParams; + diff --git a/OsmAnd-telegram/src/net/osmand/aidl/plugins/PluginParams.java b/OsmAnd-telegram/src/net/osmand/aidl/plugins/PluginParams.java new file mode 100644 index 0000000000..028bd8676a --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/plugins/PluginParams.java @@ -0,0 +1,51 @@ +package net.osmand.aidl.plugins; + +import android.os.Parcel; +import android.os.Parcelable; + +public class PluginParams implements Parcelable { + + private String pluginId; + private int newState; //0- off, 1 - on + + public PluginParams(String pluginId, int newState) { + this.pluginId = pluginId; + this.newState = newState; + } + + public String getPluginId() { + return pluginId; + } + + public int getNewState() { + return newState; + } + + protected PluginParams(Parcel in) { + pluginId = in.readString(); + newState = in.readInt(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(pluginId); + dest.writeInt(newState); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public PluginParams createFromParcel(Parcel in) { + return new PluginParams(in); + } + + @Override + public PluginParams[] newArray(int size) { + return new PluginParams[size]; + } + }; +} diff --git a/OsmAnd-telegram/src/net/osmand/aidl/tiles/ASqliteDbFile.aidl b/OsmAnd-telegram/src/net/osmand/aidl/tiles/ASqliteDbFile.aidl new file mode 100644 index 0000000000..319cfd5035 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/tiles/ASqliteDbFile.aidl @@ -0,0 +1,3 @@ +package net.osmand.aidl.tiles; + +parcelable ASqliteDbFile; \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/aidl/tiles/ASqliteDbFile.java b/OsmAnd-telegram/src/net/osmand/aidl/tiles/ASqliteDbFile.java new file mode 100644 index 0000000000..ac6f5e55d2 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/aidl/tiles/ASqliteDbFile.java @@ -0,0 +1,69 @@ +package net.osmand.aidl.tiles; + +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; + +public class ASqliteDbFile implements Parcelable { + + private String fileName; + private long modifiedTime; + private long fileSize; + private boolean active; + + public ASqliteDbFile(@NonNull String fileName, long modifiedTime, long fileSize, boolean active) { + this.fileName = fileName; + this.modifiedTime = modifiedTime; + this.fileSize = fileSize; + this.active = active; + } + + public ASqliteDbFile(Parcel in) { + readFromParcel(in); + } + + public static final Creator CREATOR = new + Creator() { + public ASqliteDbFile createFromParcel(Parcel in) { + return new ASqliteDbFile(in); + } + + public ASqliteDbFile[] newArray(int size) { + return new ASqliteDbFile[size]; + } + }; + + public String getFileName() { + return fileName; + } + + public long getModifiedTime() { + return modifiedTime; + } + + public long getFileSize() { + return fileSize; + } + + public boolean isActive() { + return active; + } + + public void writeToParcel(Parcel out, int flags) { + out.writeString(fileName); + out.writeLong(modifiedTime); + out.writeLong(fileSize); + out.writeByte((byte) (active ? 1 : 0)); + } + + private void readFromParcel(Parcel in) { + fileName = in.readString(); + modifiedTime = in.readLong(); + fileSize = in.readLong(); + active = in.readByte() != 0; + } + + public int describeContents() { + return 0; + } +} diff --git a/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt b/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt index 45e78851e2..eddcb80acb 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/TelegramApplication.kt @@ -24,7 +24,7 @@ class TelegramApplication : Application(), OsmandHelperListener { lateinit var notificationHelper: NotificationHelper private set lateinit var osmandAidlHelper: OsmandAidlHelper private set lateinit var locationProvider: TelegramLocationProvider private set - lateinit var messagesDbHelper: MessagesDbHelper private set + lateinit var locationMessages: LocationMessages private set var telegramService: TelegramService? = null @@ -67,11 +67,14 @@ class TelegramApplication : Application(), OsmandHelperListener { showLocationHelper = ShowLocationHelper(this) notificationHelper = NotificationHelper(this) locationProvider = TelegramLocationProvider(this) - messagesDbHelper = MessagesDbHelper(this) + locationMessages = LocationMessages(this) if (settings.hasAnyChatToShareLocation() && AndroidUtils.isLocationPermissionAvailable(this)) { shareLocationHelper.startSharingLocation() } + if (settings.monitoringEnabled) { + showLocationHelper.startShowingLocation() + } } fun cleanupResources() { @@ -85,6 +88,10 @@ class TelegramApplication : Application(), OsmandHelperListener { telegramHelper.stopSendingLiveLocationMessages(settings.getChatsShareInfo()) } + fun isAnyOsmAndInstalled() = TelegramSettings.AppConnect.getInstalledApps(this).isNotEmpty() + + fun isOsmAndChosen() = settings.appToConnectPackage.isNotEmpty() + fun isOsmAndInstalled() = AndroidUtils.isAppInstalled(this, settings.appToConnectPackage) val isWifiConnected: Boolean @@ -94,6 +101,13 @@ class TelegramApplication : Application(), OsmandHelperListener { return ni != null && ni.type == ConnectivityManager.TYPE_WIFI } + val isMobileConnected: Boolean + get() { + val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val ni = mgr.activeNetworkInfo + return ni != null && ni.type == ConnectivityManager.TYPE_MOBILE + } + private val isInternetConnected: Boolean get() { val mgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager diff --git a/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt b/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt index 4b90dd5a93..e1fc26e463 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/TelegramService.kt @@ -13,10 +13,11 @@ import android.os.* import android.util.Log import android.widget.Toast import net.osmand.PlatformUtil -import net.osmand.telegram.helpers.TelegramHelper.TelegramOutgoingMessagesListener import net.osmand.telegram.helpers.TelegramHelper.TelegramIncomingMessagesListener +import net.osmand.telegram.helpers.TelegramHelper.TelegramOutgoingMessagesListener import net.osmand.telegram.notifications.TelegramNotification.NotificationType import net.osmand.telegram.utils.AndroidUtils +import net.osmand.telegram.utils.OsmandLocationUtils import org.drinkless.td.libcore.telegram.TdApi import java.util.* @@ -123,6 +124,7 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis app.telegramHelper.stopLiveMessagesUpdates() app.telegramHelper.removeIncomingMessagesListener(this) app.telegramHelper.removeOutgoingMessagesListener(this) + app.settings.save() app.telegramService = null mHandlerThread.quit() @@ -202,13 +204,14 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis providers.add(0, providers.removeAt(passiveFirst)) } // find location + var location: net.osmand.Location? = null for (provider in providers) { - val location = convertLocation(service.getLastKnownLocation(provider)) - if (location != null) { - return location + val loc = convertLocation(service.getLastKnownLocation(provider)) + if (loc != null && (location == null || loc.hasAccuracy() && loc.accuracy < location.accuracy)) { + location = loc } } - return null + return location } private fun setupAlarm() { @@ -274,6 +277,11 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis override fun onReceiveChatLocationMessages(chatId: Long, vararg messages: TdApi.Message) { app().showLocationHelper.startShowMessagesTask(chatId, *messages) + messages.forEach { + if (!it.isOutgoing) { + app().locationMessages.addNewLocationMessage(it) + } + } } override fun onDeleteChatLocationMessages(chatId: Long, messages: List) { @@ -287,6 +295,12 @@ class TelegramService : Service(), LocationListener, TelegramIncomingMessagesLis override fun onUpdateMessages(messages: List) { messages.forEach { app().settings.updateShareInfo(it) + app().shareLocationHelper.checkAndSendBufferMessagesToChat(it.chatId) + if (it.sendingState == null && (it.content is TdApi.MessageLocation || it.content is TdApi.MessageText)) { + if (!it.isOutgoing) { + app().locationMessages.addNewLocationMessage(it) + } + } } } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt b/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt index 451918a91f..3b3f3146b3 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/TelegramSettings.kt @@ -7,10 +7,11 @@ import android.support.annotation.DrawableRes import android.support.annotation.StringRes import android.text.SpannableStringBuilder import android.text.style.ForegroundColorSpan -import net.osmand.data.LatLon +import net.osmand.PlatformUtil import net.osmand.telegram.helpers.OsmandAidlHelper import net.osmand.telegram.helpers.TelegramHelper import net.osmand.telegram.utils.AndroidUtils +import net.osmand.telegram.utils.OsmandApiUtils import net.osmand.telegram.utils.OsmandFormatter import net.osmand.telegram.utils.OsmandFormatter.MetricsConstants import net.osmand.telegram.utils.OsmandFormatter.SpeedConstants @@ -23,6 +24,8 @@ import java.util.concurrent.ConcurrentLinkedQueue val ADDITIONAL_ACTIVE_TIME_VALUES_SEC = listOf(15 * 60L, 30 * 60L, 60 * 60L, 180 * 60L) +const val SHARE_DEVICES_KEY = "devices" + private val SEND_MY_LOC_VALUES_SEC = listOf(1L, 2L, 3L, 5L, 10L, 15L, 30L, 60L, 90L, 2 * 60L, 3 * 60L, 5 * 60L) private val STALE_LOC_VALUES_SEC = @@ -40,9 +43,15 @@ private val LOC_HISTORY_VALUES_SEC = listOf( 24 * 60 * 60L ) +const val SHARE_TYPE_MAP = "Map" +const val SHARE_TYPE_TEXT = "Text" +const val SHARE_TYPE_MAP_AND_TEXT = "Map_and_text" +private val SHARE_TYPE_VALUES = listOf(SHARE_TYPE_MAP, SHARE_TYPE_TEXT, SHARE_TYPE_MAP_AND_TEXT) + private const val SEND_MY_LOC_DEFAULT_INDEX = 6 -private const val STALE_LOC_DEFAULT_INDEX = 4 -private const val LOC_HISTORY_DEFAULT_INDEX = 2 +private const val STALE_LOC_DEFAULT_INDEX = 0 +private const val LOC_HISTORY_DEFAULT_INDEX = 7 +private const val SHARE_TYPE_DEFAULT_INDEX = 2 private const val SETTINGS_NAME = "osmand_telegram_settings" @@ -57,6 +66,7 @@ private const val SPEED_CONSTANTS_KEY = "speed_constants" private const val SEND_MY_LOC_INTERVAL_KEY = "send_my_loc_interval" private const val STALE_LOC_TIME_KEY = "stale_loc_time" private const val LOC_HISTORY_TIME_KEY = "loc_history_time" +private const val SHARE_TYPE_KEY = "share_type" private const val APP_TO_CONNECT_PACKAGE_KEY = "app_to_connect_package" @@ -70,17 +80,24 @@ private const val SHARE_CHATS_INFO_KEY = "share_chats_info" private const val BATTERY_OPTIMISATION_ASKED = "battery_optimisation_asked" +private const val MONITORING_ENABLED = "monitoring_enabled" + private const val SHARING_INITIALIZATION_TIME = 60 * 2L // 2 minutes +private const val GPS_UPDATE_EXPIRED_TIME = 60 * 3L // 3 minutes + class TelegramSettings(private val app: TelegramApplication) { + private val log = PlatformUtil.getLog(TelegramSettings::class.java) + private var shareChatsInfo = ConcurrentHashMap() private var hiddenOnMapChats: Set = emptySet() + private var shareDevices: Set = emptySet() var sharingStatusChanges = ConcurrentLinkedQueue() - var shareDevicesIds = mutableMapOf() var currentSharingMode = "" + private set var metricsConstants = MetricsConstants.KILOMETERS_AND_METERS var speedConstants = SpeedConstants.KILOMETERS_PER_HOUR @@ -88,16 +105,19 @@ class TelegramSettings(private val app: TelegramApplication) { var sendMyLocInterval = SEND_MY_LOC_VALUES_SEC[SEND_MY_LOC_DEFAULT_INDEX] var staleLocTime = STALE_LOC_VALUES_SEC[STALE_LOC_DEFAULT_INDEX] var locHistoryTime = LOC_HISTORY_VALUES_SEC[LOC_HISTORY_DEFAULT_INDEX] + var shareTypeValue = SHARE_TYPE_VALUES[SHARE_TYPE_DEFAULT_INDEX] var appToConnectPackage = "" private set - var liveNowSortType = LiveNowSortType.SORT_BY_GROUP + var liveNowSortType = LiveNowSortType.SORT_BY_DISTANCE - val gpsAndLocPrefs = listOf(SendMyLocPref(), StaleLocPref(), LocHistoryPref()) + val gpsAndLocPrefs = listOf(SendMyLocPref(), StaleLocPref(), LocHistoryPref(), ShareTypePref()) var batteryOptimisationAsked = false + var monitoringEnabled = false + init { updatePrefs() read() @@ -107,6 +127,8 @@ class TelegramSettings(private val app: TelegramApplication) { fun isSharingLocationToChat(chatId: Long) = shareChatsInfo.containsKey(chatId) + fun isSharingLocationToUser(userId: Int) = shareChatsInfo.values.any { it.userId == userId } + fun hasAnyChatToShowOnMap() = !hiddenOnMapChats.containsAll(getLiveNowChats()) fun isShowingChatOnMap(chatId: Long) = !hiddenOnMapChats.contains(chatId) @@ -126,41 +148,69 @@ class TelegramSettings(private val app: TelegramApplication) { addActiveTime: Long = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0] ) { if (share) { - val lp: Long = when { - livePeriod < TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC -> TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong() - else -> livePeriod - } var shareChatInfo = shareChatsInfo[chatId] if (shareChatInfo == null) { shareChatInfo = ShareChatInfo() } - val currentTime = System.currentTimeMillis() / 1000 - shareChatInfo.chatId = chatId - shareChatInfo.start = currentTime - if (shareChatInfo.livePeriod == -1L) { - shareChatInfo.livePeriod = lp + val chat = app.telegramHelper.getChat(chatId) + if (chat != null && (chat.type is TdApi.ChatTypePrivate || chat.type is TdApi.ChatTypeSecret)) { + shareChatInfo.userId = app.telegramHelper.getUserIdFromChatType(chat.type) } - shareChatInfo.userSetLivePeriod = lp - shareChatInfo.userSetLivePeriodStart = currentTime - shareChatInfo.currentMessageLimit = currentTime + Math.min(lp, TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong()) - shareChatInfo.additionalActiveTime = addActiveTime + shareChatInfo.chatId = chatId + updateChatShareInfo(shareChatInfo, livePeriod, addActiveTime) shareChatsInfo[chatId] = shareChatInfo } else { shareChatsInfo.remove(chatId) } } - fun updateShareDevicesIds(list: List) { - shareDevicesIds.clear() - list.forEach { - shareDevicesIds[it.externalId] = it.deviceName + fun shareLocationToUser( + userId: Int, + livePeriod: Long = DEFAULT_VISIBLE_TIME_SECONDS, + addActiveTime: Long = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0] + ) { + val shareChatInfo = ShareChatInfo() + shareChatInfo.userId = userId + updateChatShareInfo(shareChatInfo, livePeriod, addActiveTime) + app.telegramHelper.createPrivateChatWithUser(userId, shareChatInfo, shareChatsInfo) + } + + fun updateShareDevices(list: List) { + shareDevices = list.toHashSet() + } + + fun addShareDevice(device: DeviceBot) { + val devices = shareDevices.toMutableList() + devices.add(device) + shareDevices = devices.toHashSet() + } + + fun updateCurrentSharingMode(sharingMode: String) { + if (currentSharingMode != sharingMode) { + shareChatsInfo.forEach { (_, shareInfo) -> + shareInfo.shouldSendViaBotMessage = true + } } + currentSharingMode = sharingMode } fun getChatLivePeriod(chatId: Long) = shareChatsInfo[chatId]?.livePeriod fun getChatsShareInfo() = shareChatsInfo + fun getShareDevices() = shareDevices + + fun containsShareDeviceWithName(name: String): Boolean { + shareDevices.forEach { + if (it.deviceName == name) { + return true + } + } + return false + } + + fun getCurrentSharingDevice() = shareDevices.singleOrNull { it.externalId == currentSharingMode } + fun getLastSuccessfulSendTime() = shareChatsInfo.values.maxBy { it.lastSuccessfulSendTimeMs }?.lastSuccessfulSendTimeMs ?: -1 fun stopSharingLocationToChats() { @@ -195,12 +245,56 @@ class TelegramSettings(private val app: TelegramApplication) { } fun updateShareInfo(message: TdApi.Message) { - val shareChatInfo = shareChatsInfo[message.chatId] + val shareInfo = shareChatsInfo[message.chatId] val content = message.content - if (shareChatInfo != null && content is TdApi.MessageLocation) { - shareChatInfo.currentMessageId = message.id - shareChatInfo.lastSuccessfulLocation = LatLon(content.location.latitude, content.location.longitude) - shareChatInfo.lastSuccessfulSendTimeMs = Math.max(message.editDate, message.date) * 1000L + if (shareInfo != null) { + when (content) { + is TdApi.MessageLocation -> { + val state = message.sendingState + if (state != null) { + if (state.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR) { + shareInfo.pendingMapMessage = true + log.debug("updateShareInfo MAP ${message.id} MessageSendingStatePending") + shareInfo.oldMapMessageId = message.id + } else if (state.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR) { + shareInfo.hasSharingError = true + shareInfo.pendingMapMessage = false + log.debug("updateShareInfo MAP ${message.id} MessageSendingStateFailed") + } + } else { + shareInfo.currentMapMessageId = message.id + shareInfo.pendingMapMessage = false + shareInfo.pendingTdLib-- + shareInfo.lastSuccessfulSendTimeMs = Math.max(message.editDate, message.date) * 1000L + if (shareTypeValue == SHARE_TYPE_MAP) { + shareInfo.sentMessages++ + } + log.debug("updateShareInfo MAP ${message.id} SUCCESS pendingTdLib: ${shareInfo.pendingTdLib}") + } + } + is TdApi.MessageText -> { + val state = message.sendingState + if (state != null) { + if (state.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR) { + log.debug("updateShareInfo TEXT ${message.id} MessageSendingStatePending") + shareInfo.pendingTextMessage = true + shareInfo.oldTextMessageId = message.id + } else if (state.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR) { + log.debug("updateShareInfo TEXT ${message.id} MessageSendingStateFailed") + shareInfo.hasSharingError = true + shareInfo.pendingTextMessage = false + } + } else { + shareInfo.currentTextMessageId = message.id + shareInfo.updateTextMessageId++ + shareInfo.pendingTextMessage = false + shareInfo.pendingTdLib-- + shareInfo.sentMessages++ + shareInfo.lastSuccessfulSendTimeMs = Math.max(message.editDate, message.date) * 1000L + log.debug("updateShareInfo TEXT ${message.id} SUCCESS pendingTdLib: ${shareInfo.pendingTdLib}") + } + } + } } } @@ -216,11 +310,15 @@ class TelegramSettings(private val app: TelegramApplication) { statusChangeTime = newSharingStatus.statusChangeTime locationTime = newSharingStatus.locationTime chatsTitles = newSharingStatus.chatsTitles + title = newSharingStatus.title if (statusType == SharingStatusType.INITIALIZING - && newSharingStatus.statusType == SharingStatusType.INITIALIZING - && !lastSharingStatus.description.contains(newSharingStatus.description)) { - lastSharingStatus.description = "${lastSharingStatus.description}, ${newSharingStatus.description}" + && newSharingStatus.statusType == SharingStatusType.INITIALIZING) { + if (!description.contains(newSharingStatus.description)) { + description = "$description, ${newSharingStatus.description}" + } + } else { + description = newSharingStatus.description } } } @@ -229,12 +327,44 @@ class TelegramSettings(private val app: TelegramApplication) { } } + private fun updateChatShareInfo( + shareChatInfo: ShareChatInfo, + livePeriod: Long = DEFAULT_VISIBLE_TIME_SECONDS, + addActiveTime: Long = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0] + ) { + val lp: Long = when { + livePeriod < TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC -> TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong() + else -> livePeriod + } + val currentTime = System.currentTimeMillis() / 1000 + val user = app.telegramHelper.getCurrentUser() + if (user != null && currentSharingMode != user.id.toString() && shareChatInfo.start == -1L) { + shareChatInfo.shouldSendViaBotMessage = true + } + + shareChatInfo.start = currentTime + if (shareChatInfo.livePeriod == -1L) { + shareChatInfo.livePeriod = lp + } + shareChatInfo.userSetLivePeriod = lp + shareChatInfo.userSetLivePeriodStart = currentTime + shareChatInfo.currentMessageLimit = currentTime + Math.min(lp, TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong()) + shareChatInfo.additionalActiveTime = addActiveTime + } + private fun getNewSharingStatusHistoryItem(): SharingStatus { return SharingStatus().apply { statusChangeTime = System.currentTimeMillis() val lm = app.getSystemService(Context.LOCATION_SERVICE) as LocationManager val gpsEnabled = try { - lm.isProviderEnabled(LocationManager.GPS_PROVIDER) + if (lm.isProviderEnabled(LocationManager.GPS_PROVIDER)) { + val loc = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER) + val gpsActive = loc != null && ((statusChangeTime - loc.time) / 1000) < GPS_UPDATE_EXPIRED_TIME + val lastSentLocationExpired = ((statusChangeTime - app.shareLocationHelper.lastLocationUpdateTime) / 1000) > GPS_UPDATE_EXPIRED_TIME + (gpsActive || !lastSentLocationExpired) + } else { + false + } } catch (ex: Exception) { false } @@ -263,7 +393,10 @@ class TelegramSettings(private val app: TelegramApplication) { } else if (!initializing) { when { !gpsEnabled -> { - locationTime = app.shareLocationHelper.lastLocationMessageSentTime + locationTime = app.shareLocationHelper.lastLocationUpdateTime + if (locationTime <= 0) { + locationTime = getLastSuccessfulSendTime() + } title = app.getString(R.string.no_gps_connection) description = app.getString(R.string.last_updated_location) statusType = SharingStatusType.NO_GPS @@ -276,9 +409,14 @@ class TelegramSettings(private val app: TelegramApplication) { } else -> { locationTime = getLastSuccessfulSendTime() - title = app.getString(R.string.successfully_sent_and_updated) - description = app.getString(R.string.last_updated_location) - statusType = SharingStatusType.SUCCESSFULLY_SENT + statusType = SharingStatusType.SENDING + if (locationTime == -1L) { + title = app.getString(R.string.sending_location_messages) + description = app.getString(R.string.waiting_for_response_from_telegram) + } else { + title = app.getString(R.string.successfully_sent_and_updated) + description = app.getString(R.string.last_updated_location) + } } } } else { @@ -300,9 +438,14 @@ class TelegramSettings(private val app: TelegramApplication) { } fun onDeleteLiveMessages(chatId: Long, messages: List) { - val currentMessageId = shareChatsInfo[chatId]?.currentMessageId - if (messages.contains(currentMessageId)) { - shareChatsInfo[chatId]?.currentMessageId = -1 + val currentMapMessageId = shareChatsInfo[chatId]?.currentMapMessageId + if (messages.contains(currentMapMessageId)) { + shareChatsInfo[chatId]?.currentMapMessageId = -1 + } + val currentTextMessageId = shareChatsInfo[chatId]?.currentTextMessageId + if (messages.contains(currentTextMessageId)) { + shareChatsInfo[chatId]?.currentTextMessageId = -1 + shareChatsInfo[chatId]?.updateTextMessageId = 1 } } @@ -326,28 +469,24 @@ class TelegramSettings(private val app: TelegramApplication) { edit.putLong(STALE_LOC_TIME_KEY, staleLocTime) edit.putLong(LOC_HISTORY_TIME_KEY, locHistoryTime) + edit.putString(SHARE_TYPE_KEY, shareTypeValue) + edit.putString(APP_TO_CONNECT_PACKAGE_KEY, appToConnectPackage) edit.putString(LIVE_NOW_SORT_TYPE_KEY, liveNowSortType.name) edit.putBoolean(BATTERY_OPTIMISATION_ASKED, batteryOptimisationAsked) - try { - val jArray = JSONArray() - shareChatsInfo.forEach { (chatId, chatInfo) -> - val obj = JSONObject() - obj.put(ShareChatInfo.CHAT_ID_KEY, chatId) - obj.put(ShareChatInfo.START_KEY, chatInfo.start) - obj.put(ShareChatInfo.LIVE_PERIOD_KEY, chatInfo.livePeriod) - obj.put(ShareChatInfo.LIMIT_KEY, chatInfo.currentMessageLimit) - obj.put(ShareChatInfo.CURRENT_MESSAGE_ID_KEY, chatInfo.currentMessageId) - obj.put(ShareChatInfo.USER_SET_LIVE_PERIOD_KEY, chatInfo.userSetLivePeriod) - obj.put(ShareChatInfo.USER_SET_LIVE_PERIOD_START_KEY, chatInfo.userSetLivePeriodStart) - jArray.put(obj) - } + edit.putBoolean(MONITORING_ENABLED, monitoringEnabled) + + val jArray = convertShareChatsInfoToJson() + if (jArray != null) { edit.putString(SHARE_CHATS_INFO_KEY, jArray.toString()) - } catch (e: JSONException) { - e.printStackTrace() + } + + val jsonObject = convertShareDevicesToJson() + if (jsonObject != null) { + edit.putString(SHARE_DEVICES_KEY, jsonObject.toString()) } edit.apply() @@ -376,22 +515,80 @@ class TelegramSettings(private val app: TelegramApplication) { e.printStackTrace() } + parseShareDevices(prefs.getString(SHARE_DEVICES_KEY, "")) + val sendMyLocDef = SEND_MY_LOC_VALUES_SEC[SEND_MY_LOC_DEFAULT_INDEX] sendMyLocInterval = prefs.getLong(SEND_MY_LOC_INTERVAL_KEY, sendMyLocDef) val staleLocDef = STALE_LOC_VALUES_SEC[STALE_LOC_DEFAULT_INDEX] staleLocTime = prefs.getLong(STALE_LOC_TIME_KEY, staleLocDef) val locHistoryDef = LOC_HISTORY_VALUES_SEC[LOC_HISTORY_DEFAULT_INDEX] locHistoryTime = prefs.getLong(LOC_HISTORY_TIME_KEY, locHistoryDef) + val shareTypeDef = SHARE_TYPE_VALUES[SHARE_TYPE_DEFAULT_INDEX] + shareTypeValue = prefs.getString(SHARE_TYPE_KEY, shareTypeDef) currentSharingMode = prefs.getString(SHARING_MODE_KEY, "") - appToConnectPackage = prefs.getString(APP_TO_CONNECT_PACKAGE_KEY, "") + val defPackage = if (AppConnect.getInstalledApps(app).size == 1) AppConnect.getInstalledApps(app).first().appPackage else "" + appToConnectPackage = prefs.getString(APP_TO_CONNECT_PACKAGE_KEY, defPackage) liveNowSortType = LiveNowSortType.valueOf( - prefs.getString(LIVE_NOW_SORT_TYPE_KEY, LiveNowSortType.SORT_BY_GROUP.name) + prefs.getString(LIVE_NOW_SORT_TYPE_KEY, LiveNowSortType.SORT_BY_DISTANCE.name) ) batteryOptimisationAsked = prefs.getBoolean(BATTERY_OPTIMISATION_ASKED,false) + + monitoringEnabled = prefs.getBoolean(MONITORING_ENABLED,false) + } + + private fun convertShareDevicesToJson():JSONObject?{ + return try { + val jsonObject = JSONObject() + val jArray = JSONArray() + shareDevices.forEach { device -> + val obj = JSONObject() + obj.put(DeviceBot.DEVICE_ID, device.id) + obj.put(DeviceBot.USER_ID, device.userId) + obj.put(DeviceBot.CHAT_ID, device.chatId) + obj.put(DeviceBot.DEVICE_NAME, device.deviceName) + obj.put(DeviceBot.EXTERNAL_ID, device.externalId) + obj.put(DeviceBot.DATA, JSONObject(device.data)) + jArray.put(obj) + } + jsonObject.put(SHARE_DEVICES_KEY, jArray) + } catch (e: JSONException) { + e.printStackTrace() + null + } + } + + private fun convertShareChatsInfoToJson(): JSONArray? { + return try { + val jArray = JSONArray() + shareChatsInfo.forEach { (chatId, chatInfo) -> + val obj = JSONObject() + obj.put(ShareChatInfo.CHAT_ID_KEY, chatId) + obj.put(ShareChatInfo.USER_ID_KEY, chatInfo.userId) + obj.put(ShareChatInfo.START_KEY, chatInfo.start) + obj.put(ShareChatInfo.LIVE_PERIOD_KEY, chatInfo.livePeriod) + obj.put(ShareChatInfo.LIMIT_KEY, chatInfo.currentMessageLimit) + obj.put(ShareChatInfo.UPDATE_TEXT_MESSAGE_ID_KEY, chatInfo.updateTextMessageId) + obj.put(ShareChatInfo.CURRENT_MAP_MESSAGE_ID_KEY, chatInfo.currentMapMessageId) + obj.put(ShareChatInfo.CURRENT_TEXT_MESSAGE_ID_KEY, chatInfo.currentTextMessageId) + obj.put(ShareChatInfo.USER_SET_LIVE_PERIOD_KEY, chatInfo.userSetLivePeriod) + obj.put(ShareChatInfo.USER_SET_LIVE_PERIOD_START_KEY, chatInfo.userSetLivePeriodStart) + obj.put(ShareChatInfo.LAST_SUCCESSFUL_SEND_TIME_KEY, chatInfo.lastSuccessfulSendTimeMs) + obj.put(ShareChatInfo.LAST_SEND_MAP_TIME_KEY, chatInfo.lastSendMapMessageTime) + obj.put(ShareChatInfo.LAST_SEND_TEXT_TIME_KEY, chatInfo.lastSendTextMessageTime) + obj.put(ShareChatInfo.PENDING_TEXT_MESSAGE_KEY, chatInfo.pendingTextMessage) + obj.put(ShareChatInfo.PENDING_MAP_MESSAGE_KEY, chatInfo.pendingMapMessage) + obj.put(ShareChatInfo.SENT_MESSAGES_KEY, chatInfo.sentMessages) + jArray.put(obj) + } + jArray + } catch (e: JSONException) { + e.printStackTrace() + null + } } private fun parseShareChatsInfo(json: JSONArray) { @@ -399,17 +596,30 @@ class TelegramSettings(private val app: TelegramApplication) { val obj = json.getJSONObject(i) val shareInfo = ShareChatInfo().apply { chatId = obj.optLong(ShareChatInfo.CHAT_ID_KEY) + userId = obj.optInt(ShareChatInfo.USER_ID_KEY) start = obj.optLong(ShareChatInfo.START_KEY) livePeriod = obj.optLong(ShareChatInfo.LIVE_PERIOD_KEY) currentMessageLimit = obj.optLong(ShareChatInfo.LIMIT_KEY) - currentMessageId = obj.optLong(ShareChatInfo.CURRENT_MESSAGE_ID_KEY) + updateTextMessageId = obj.optInt(ShareChatInfo.UPDATE_TEXT_MESSAGE_ID_KEY) + currentMapMessageId = obj.optLong(ShareChatInfo.CURRENT_MAP_MESSAGE_ID_KEY) + currentTextMessageId = obj.optLong(ShareChatInfo.CURRENT_TEXT_MESSAGE_ID_KEY) userSetLivePeriod = obj.optLong(ShareChatInfo.USER_SET_LIVE_PERIOD_KEY) userSetLivePeriodStart = obj.optLong(ShareChatInfo.USER_SET_LIVE_PERIOD_START_KEY) + lastSuccessfulSendTimeMs = obj.optLong(ShareChatInfo.LAST_SUCCESSFUL_SEND_TIME_KEY) + lastSendMapMessageTime = obj.optInt(ShareChatInfo.LAST_SEND_MAP_TIME_KEY) + lastSendTextMessageTime = obj.optInt(ShareChatInfo.LAST_SEND_TEXT_TIME_KEY) + pendingTextMessage = obj.optBoolean(ShareChatInfo.PENDING_TEXT_MESSAGE_KEY) + pendingMapMessage = obj.optBoolean(ShareChatInfo.PENDING_MAP_MESSAGE_KEY) + sentMessages = obj.optInt(ShareChatInfo.SENT_MESSAGES_KEY) } shareChatsInfo[shareInfo.chatId] = shareInfo } } + private fun parseShareDevices(json: String) { + shareDevices = OsmandApiUtils.parseJsonContents(json).toHashSet() + } + private fun getLiveNowChats() = app.telegramHelper.getMessagesByChatIds(locHistoryTime).keys private fun updatePrefs() { @@ -473,6 +683,41 @@ class TelegramSettings(private val app: TelegramApplication) { } } + inner class ShareTypePref : DurationPref( + R.drawable.ic_action_location_history, + R.string.send_location_as, + R.string.send_location_as_descr, + emptyList() + ) { + + override fun getCurrentValue(): String { + return getTextValue(shareTypeValue) + } + + override fun setCurrentValue(index: Int) { + val newSharingType = SHARE_TYPE_VALUES[index] + if (shareTypeValue != newSharingType && app.telegramHelper.getCurrentUser()?.id.toString() != currentSharingMode) { + shareChatsInfo.forEach { (_, shareInfo) -> + shareInfo.shouldSendViaBotMessage = true + } + } + shareTypeValue = newSharingType + } + + override fun getMenuItems(): List { + return SHARE_TYPE_VALUES.map { getTextValue(it) } + } + + private fun getTextValue(shareType: String): String { + return when (shareType) { + SHARE_TYPE_MAP -> app.getString(R.string.shared_string_map) + SHARE_TYPE_TEXT -> app.getString(R.string.shared_string_text) + SHARE_TYPE_MAP_AND_TEXT -> app.getString(R.string.map_and_text) + else -> "" + } + } + } + abstract inner class DurationPref( @DrawableRes val iconId: Int, @StringRes val titleId: Int, @@ -484,7 +729,7 @@ class TelegramSettings(private val app: TelegramApplication) { abstract fun setCurrentValue(index: Int) - fun getMenuItems() = values.map { OsmandFormatter.getFormattedDuration(app, it) } + open fun getMenuItems() = values.map { OsmandFormatter.getFormattedDuration(app, it) } } enum class AppConnect( @@ -528,6 +773,16 @@ class TelegramSettings(private val app: TelegramApplication) { return 0 } + @DrawableRes + fun getIconId(appPackage: String): Int { + for (item in values()) { + if (item.appPackage == appPackage) { + return item.iconId + } + } + return 0 + } + fun getInstalledApps(context: Context) = values().filter { AndroidUtils.isAppInstalled(context, it.appPackage) } } @@ -567,11 +822,6 @@ class TelegramSettings(private val app: TelegramApplication) { R.color.sharing_status_icon_error, true ), - SUCCESSFULLY_SENT( - R.drawable.ic_action_share_location, - R.color.sharing_status_icon_success, - false - ), SENDING( R.drawable.ic_action_share_location, R.color.sharing_status_icon_success, @@ -601,6 +851,16 @@ class TelegramSettings(private val app: TelegramApplication) { var deviceName: String = "" var externalId: String = "" var data: String = "" + + companion object { + + internal const val DEVICE_ID = "id" + internal const val USER_ID = "userId" + internal const val CHAT_ID = "chatId" + internal const val DEVICE_NAME = "deviceName" + internal const val EXTERNAL_ID = "externalId" + internal const val DATA = "data" + } } class SharingStatus { @@ -634,17 +894,29 @@ class TelegramSettings(private val app: TelegramApplication) { class ShareChatInfo { var chatId = -1L + var userId = -1 var start = -1L var livePeriod = -1L + var updateTextMessageId = 1 var currentMessageLimit = -1L - var currentMessageId = -1L + var currentMapMessageId = -1L + var oldMapMessageId = -1L + var currentTextMessageId = -1L + var oldTextMessageId = -1L var userSetLivePeriod = -1L var userSetLivePeriodStart = -1L - var lastSuccessfulLocation: LatLon? = null var lastSuccessfulSendTimeMs = -1L - var shouldDeletePreviousMessage = false - var additionalActiveTime = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0] + var lastSendTextMessageTime = -1 + var lastSendMapMessageTime = -1 + var sentMessages = 0 + var pendingTdLib = 0 + var pendingTextMessage = false + var pendingMapMessage = false + var shouldSendViaBotMessage = false var hasSharingError = false + var shouldDeletePreviousMapMessage = false + var shouldDeletePreviousTextMessage = false + var additionalActiveTime = ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0] fun getNextAdditionalActiveTime(): Long { var index = ADDITIONAL_ACTIVE_TIME_VALUES_SEC.indexOf(additionalActiveTime) @@ -662,12 +934,21 @@ class TelegramSettings(private val app: TelegramApplication) { companion object { internal const val CHAT_ID_KEY = "chatId" + internal const val USER_ID_KEY = "userId" internal const val START_KEY = "start" internal const val LIVE_PERIOD_KEY = "livePeriod" internal const val LIMIT_KEY = "limit" - internal const val CURRENT_MESSAGE_ID_KEY = "currentMessageId" + internal const val UPDATE_TEXT_MESSAGE_ID_KEY = "updateTextMessageId" + internal const val CURRENT_MAP_MESSAGE_ID_KEY = "currentMapMessageId" + internal const val CURRENT_TEXT_MESSAGE_ID_KEY = "currentTextMessageId" internal const val USER_SET_LIVE_PERIOD_KEY = "userSetLivePeriod" internal const val USER_SET_LIVE_PERIOD_START_KEY = "userSetLivePeriodStart" + internal const val LAST_SUCCESSFUL_SEND_TIME_KEY = "lastSuccessfulSendTime" + internal const val LAST_SEND_MAP_TIME_KEY = "lastSendMapMessageTime" + internal const val LAST_SEND_TEXT_TIME_KEY = "lastSendTextMessageTime" + internal const val PENDING_TEXT_MESSAGE_KEY = "pendingTextMessage" + internal const val PENDING_MAP_MESSAGE_KEY = "pendingMapMessage" + internal const val SENT_MESSAGES_KEY = "sentMessages" } } } \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/LocationMessages.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/LocationMessages.kt new file mode 100644 index 0000000000..421ada72bf --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/LocationMessages.kt @@ -0,0 +1,517 @@ +package net.osmand.telegram.helpers + +import android.content.Context +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import net.osmand.Location +import net.osmand.PlatformUtil +import net.osmand.data.LatLon +import net.osmand.telegram.TelegramApplication +import net.osmand.telegram.utils.OsmandLocationUtils +import net.osmand.util.MapUtils +import org.drinkless.td.libcore.telegram.TdApi + +class LocationMessages(val app: TelegramApplication) { + + private val log = PlatformUtil.getLog(LocationMessages::class.java) + + private var bufferedMessages = emptyList() + + private var lastLocationPoints = mutableMapOf() + + private val dbHelper: SQLiteHelper + + init { + dbHelper = SQLiteHelper(app) + readBufferedMessages() + } + + fun getBufferedMessages(): List { + return bufferedMessages.sortedBy { it.time } + } + + fun getBufferedMessagesForChat(chatId: Long): List { + return bufferedMessages.filter { it.chatId==chatId }.sortedBy { it.time } + } + + fun getIngoingMessages(currentUserId: Int, start: Long, end: Long): List { + return dbHelper.getIngoingMessages(currentUserId, start, end) + } + + fun getIngoingUserLocations(start: Long, end: Long): List { + return dbHelper.getIngoingUserLocations(start, end) + } + + fun getMessagesForUserInChat(userId: Int, chatId: Long, start: Long, end: Long): List { + return dbHelper.getMessagesForUserInChat(userId, chatId, start, end) + } + + fun getMessagesForUser(userId: Int, start: Long, end: Long): List { + return dbHelper.getMessagesForUser(userId, start, end) + } + + fun addBufferedMessage(message: BufferMessage) { + log.debug("addBufferedMessage $message") + val messages = mutableListOf(*this.bufferedMessages.toTypedArray()) + messages.add(message) + this.bufferedMessages = messages + dbHelper.addBufferedMessage(message) + } + + fun addNewLocationMessage(message: TdApi.Message) { + log.debug("addNewLocationMessage ${message.id}") + val type = OsmandLocationUtils.getMessageType(message, app.telegramHelper) + + val newItem = LocationHistoryPoint(message.senderUserId, message.chatId, type) + val previousMessageLatLon = lastLocationPoints[newItem] + val locationMessage = OsmandLocationUtils.parseMessage(message, app.telegramHelper, previousMessageLatLon) + if (locationMessage != null) { + dbHelper.addLocationMessage(locationMessage) + lastLocationPoints[newItem] = LatLon(locationMessage.lat, locationMessage.lon) + } + } + + fun addMyLocationMessage(loc: Location) { + log.debug("addMyLocationMessage") + val currentUserId = app.telegramHelper.getCurrentUserId() + val newItem = LocationHistoryPoint(currentUserId, 0, -1) + val previousMessageLatLon = lastLocationPoints[newItem] + val distance = if (previousMessageLatLon != null) { MapUtils.getDistance(previousMessageLatLon, loc.latitude, loc.longitude) } else 0.0 + val message = LocationMessages.LocationMessage(currentUserId, 0, loc.latitude, loc.longitude, loc.altitude, + loc.speed.toDouble(), loc.accuracy.toDouble(), loc.bearing.toDouble(), loc.time, TYPE_MY_LOCATION, 0, distance) + + dbHelper.addLocationMessage(message) + lastLocationPoints[newItem] = LatLon(message.lat, message.lon) + } + + fun clearBufferedMessages() { + log.debug("clearBufferedMessages") + dbHelper.clearBufferedMessages() + bufferedMessages = emptyList() + } + + fun removeBufferedMessage(message: BufferMessage) { + log.debug("removeBufferedMessage $message") + val messages = mutableListOf(*this.bufferedMessages.toTypedArray()) + messages.remove(message) + this.bufferedMessages = messages + dbHelper.removeBufferedMessage(message) + } + + fun getBufferedMessagesCount(): Int { + return bufferedMessages.size + } + + fun getBufferedMessagesCountForChat(chatId: Long): Int { + return bufferedMessages.count { it.chatId == chatId } + } + + private fun readBufferedMessages() { + this.bufferedMessages = dbHelper.getBufferedMessages() + } + + private fun readLastMessages() { + this.lastLocationPoints = dbHelper.getLastMessages() + } + + private class SQLiteHelper(context: Context) : + SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { + + override fun onCreate(db: SQLiteDatabase) { + db.execSQL(TIMELINE_TABLE_CREATE) + db.execSQL("CREATE INDEX IF NOT EXISTS $DATE_INDEX ON $TIMELINE_TABLE_NAME (\"$COL_TIME\" DESC);") + db.execSQL(BUFFER_TABLE_CREATE) + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL(TIMELINE_TABLE_DELETE) + db.execSQL(BUFFER_TABLE_DELETE) + onCreate(db) + } + + internal fun addBufferedMessage(message: BufferMessage) { + writableDatabase?.execSQL(BUFFER_TABLE_INSERT, + arrayOf(message.chatId, message.lat, message.lon, message.altitude, message.speed, + message.hdop, message.bearing, message.time, message.type)) + } + + internal fun addLocationMessage(message: LocationMessage) { + writableDatabase?.execSQL(TIMELINE_TABLE_INSERT, + arrayOf(message.userId, message.chatId, message.lat, message.lon, message.altitude, message.speed, + message.hdop, message.bearing, message.time, message.type, message.messageId, message.distanceFromPrev)) + } + + internal fun getMessagesForUser(userId: Int, start: Long, end: Long): List { + val res = arrayListOf() + readableDatabase?.rawQuery( + "$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID = ? AND $COL_TIME BETWEEN $start AND $end ORDER BY $COL_CHAT_ID ASC, $COL_TYPE DESC, $COL_TIME ASC ", + arrayOf(userId.toString()))?.apply { + if (moveToFirst()) { + do { + res.add(readLocationMessage(this@apply)) + } while (moveToNext()) + } + close() + } + return res + } + + internal fun getPreviousMessage(userId: Int, chatId: Long): LocationMessage? { + var res:LocationMessage? = null + readableDatabase?.rawQuery( + "$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID = ? AND $COL_CHAT_ID = ? ORDER BY $COL_TIME DESC LIMIT 1", + arrayOf(userId.toString(), chatId.toString()))?.apply { + if (moveToFirst()) { + res = readLocationMessage(this@apply) + } + close() + } + return res + } + + internal fun getIngoingMessages(currentUserId: Int, start: Long, end: Long): List { + val res = arrayListOf() + readableDatabase?.rawQuery( + "$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID != ? AND $COL_TIME BETWEEN $start AND $end ORDER BY $COL_USER_ID, $COL_CHAT_ID, $COL_TYPE DESC, $COL_TIME ", + arrayOf(currentUserId.toString()))?.apply { + if (moveToFirst()) { + do { + res.add(readLocationMessage(this@apply)) + } while (moveToNext()) + } + close() + } + return res + } + + internal fun getIngoingUserLocations(start: Long, end: Long): List { + val res = arrayListOf() + readableDatabase?.rawQuery( + "$TIMELINE_TABLE_SELECT WHERE $COL_TIME BETWEEN $start AND $end ORDER BY $COL_USER_ID, $COL_CHAT_ID, $COL_TYPE DESC, $COL_TIME ", null + )?.apply { + if (moveToFirst()) { + var userId = -1 + var chatId = -1L + // TODO query bot name + var botName = "" + var userLocations: UserLocations? = null + var userLocationsMap: MutableMap>? = null + var userLocationsListByType: MutableList? = null + var segment: UserTrkSegment? = null + do { + val locationMessage = readLocationMessage(this@apply) + userId = locationMessage.userId + chatId = locationMessage.chatId + // TODO compare bot name as well + if(userLocations == null || userLocations.userId != userId || + userLocations.chatId != chatId) { + userLocationsMap = mutableMapOf() + userLocations = UserLocations(userId, chatId, botName, userLocationsMap) + res.add(userLocations) + segment = null + } + if(segment == null || + segment.type != locationMessage.type || locationMessage.time - segment.maxTime > 30 * 1000 * 60) { + segment = UserTrkSegment(mutableListOf(), 0.0, locationMessage.type, + locationMessage.time, locationMessage.time) + if(userLocationsMap!![segment.type] == null) { + userLocationsMap[segment.type] = mutableListOf() + } + userLocationsMap[segment.type]!!.add(segment) + } + if(segment.points.size > 0) { + segment.distance += MapUtils.getDistance(locationMessage.lat, + locationMessage.lon, segment.points.last().lat, segment.points.last().lon) + } + segment.maxTime = locationMessage.time + segment.points.add(locationMessage) + } while (moveToNext()) + } + close() + } + return res + } + + internal fun getMessagesForUserInChat(userId: Int, chatId: Long, start: Long, end: Long): List { + val res = arrayListOf() + readableDatabase?.rawQuery( + "$TIMELINE_TABLE_SELECT WHERE $COL_USER_ID = ? AND $COL_CHAT_ID = ? AND $COL_TIME BETWEEN $start AND $end ORDER BY $COL_TYPE DESC, $COL_TIME ", + arrayOf(userId.toString(), chatId.toString()))?.apply { + if (moveToFirst()) { + do { + res.add(readLocationMessage(this@apply)) + } while (moveToNext()) + } + close() + } + return res + } + + internal fun getBufferedMessages(): List { + val res = arrayListOf() + readableDatabase?.rawQuery(BUFFER_TABLE_SELECT, null)?.apply { + if (moveToFirst()) { + do { + res.add(readBufferMessage(this@apply)) + } while (moveToNext()) + } + close() + } + return res + } + + internal fun getLastMessages(): MutableMap { + val res = mutableMapOf() + readableDatabase?.rawQuery("$TIMELINE_TABLE_SELECT_HISTORY_POINTS ORDER BY $COL_TIME ASC", null)?.apply { + if (moveToFirst()) { + do { +// res.add(readLocationMessage(this@apply)) + } while (moveToNext()) + } + close() + } + return res + } + + internal fun readLocationMessage(cursor: Cursor): LocationMessage { + val userId = cursor.getInt(0) + val chatId = cursor.getLong(1) + val lat = cursor.getDouble(2) + val lon = cursor.getDouble(3) + val altitude = cursor.getDouble(4) + val speed = cursor.getDouble(5) + val hdop = cursor.getDouble(6) + val bearing = cursor.getDouble(7) + val date = cursor.getLong(8) + val type = cursor.getInt(9) + val messageId = cursor.getLong(10) + val distanceFromPrev = cursor.getDouble(11) + + return LocationMessage(userId, chatId, lat, lon, altitude, speed, hdop, bearing, date, type, messageId, distanceFromPrev) + } + + internal fun readBufferMessage(cursor: Cursor): BufferMessage { + val chatId = cursor.getLong(0) + val lat = cursor.getDouble(1) + val lon = cursor.getDouble(2) + val altitude = cursor.getDouble(3) + val speed = cursor.getDouble(4) + val hdop = cursor.getDouble(5) + val bearing = cursor.getDouble(6) + val date = cursor.getLong(7) + val type = cursor.getInt(8) + + return BufferMessage(chatId, lat, lon, altitude, speed, hdop, bearing, date, type) + } + + internal fun readLocationHistoryPoint(cursor: Cursor): Pair { + val userId = cursor.getInt(0) + val chatId = cursor.getLong(1) + val lat = cursor.getDouble(2) + val lon = cursor.getDouble(3) + val type = cursor.getInt(4) + + return Pair(LocationHistoryPoint(userId, chatId, type), LatLon(lat, lon)) + } + + internal fun clearBufferedMessages() { + writableDatabase?.execSQL(BUFFER_TABLE_CLEAR) + } + + internal fun removeBufferedMessage(message: BufferMessage) { + + writableDatabase?.execSQL( + BUFFER_TABLE_REMOVE, + arrayOf( + message.chatId, + message.lat, + message.lon, + message.altitude, + message.speed, + message.hdop, + message.bearing, + message.time, + message.type + ) + ) + } + + companion object { + + private const val DATABASE_NAME = "location_messages" + private const val DATABASE_VERSION = 5 + + private const val TIMELINE_TABLE_NAME = "timeline" + private const val BUFFER_TABLE_NAME = "buffer" + + private const val COL_USER_ID = "user_id" + private const val COL_CHAT_ID = "chat_id" + private const val COL_TIME = "time" + private const val COL_LAT = "lat" + private const val COL_LON = "lon" + private const val COL_ALTITUDE = "altitude" + private const val COL_SPEED = "speed" + private const val COL_HDOP = "hdop" + private const val COL_BEARING = "bearing" + private const val COL_TYPE = "type" // 0 = user map message, 1 = user text message, 2 = bot map message, 3 = bot text message + private const val COL_MESSAGE_ID = "message_id" + private const val COL_DISTANCE_FROM_PREV = "distance_from_prev" + + private const val DATE_INDEX = "date_index" + + // Timeline messages table + private const val TIMELINE_TABLE_INSERT = + ("INSERT INTO $TIMELINE_TABLE_NAME ($COL_USER_ID, $COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE, $COL_MESSAGE_ID, $COL_DISTANCE_FROM_PREV) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") + + private const val TIMELINE_TABLE_CREATE = + ("CREATE TABLE IF NOT EXISTS $TIMELINE_TABLE_NAME ($COL_USER_ID long, $COL_CHAT_ID long,$COL_LAT double, $COL_LON double, $COL_ALTITUDE double, $COL_SPEED float, $COL_HDOP double, $COL_BEARING double, $COL_TIME long, $COL_TYPE int, $COL_MESSAGE_ID long, $COL_DISTANCE_FROM_PREV double )") + + private const val TIMELINE_TABLE_SELECT = + "SELECT $COL_USER_ID, $COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE, $COL_MESSAGE_ID, $COL_DISTANCE_FROM_PREV FROM $TIMELINE_TABLE_NAME" + + private const val TIMELINE_TABLE_SELECT_HISTORY_POINTS = + "SELECT $COL_USER_ID, $COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_TIME, $COL_TYPE FROM $TIMELINE_TABLE_NAME" + + private const val TIMELINE_TABLE_CLEAR = "DELETE FROM $TIMELINE_TABLE_NAME" + + private const val TIMELINE_TABLE_DELETE = "DROP TABLE IF EXISTS $TIMELINE_TABLE_NAME" + + // Buffer messages table + private const val BUFFER_TABLE_INSERT = + ("INSERT INTO $BUFFER_TABLE_NAME ($COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") + + private const val BUFFER_TABLE_CREATE = + ("CREATE TABLE IF NOT EXISTS $BUFFER_TABLE_NAME ($COL_CHAT_ID long, $COL_LAT double, $COL_LON double, $COL_ALTITUDE double, $COL_SPEED float, $COL_HDOP double, $COL_BEARING double, $COL_TIME long, $COL_TYPE int)") + + private const val BUFFER_TABLE_SELECT = + "SELECT $COL_CHAT_ID, $COL_LAT, $COL_LON, $COL_ALTITUDE, $COL_SPEED, $COL_HDOP, $COL_BEARING, $COL_TIME, $COL_TYPE FROM $BUFFER_TABLE_NAME" + + private const val BUFFER_TABLE_CLEAR = "DELETE FROM $BUFFER_TABLE_NAME" + + private const val BUFFER_TABLE_REMOVE = "DELETE FROM $BUFFER_TABLE_NAME WHERE $COL_CHAT_ID = ? AND $COL_LAT = ? AND $COL_LON = ? AND $COL_ALTITUDE = ? AND $COL_SPEED = ? AND $COL_HDOP = ? AND $COL_BEARING = ? AND $COL_TIME = ? AND $COL_TYPE = ?" + + private const val BUFFER_TABLE_DELETE = "DROP TABLE IF EXISTS $BUFFER_TABLE_NAME" + } + } + + data class LocationMessage( + val userId: Int, + val chatId: Long, + val lat: Double, + val lon: Double, + val altitude: Double, + val speed: Double, + val hdop: Double, + val bearing: Double, + val time: Long, + val type: Int, + val messageId: Long, + val distanceFromPrev: Double) + + data class BufferMessage ( + val chatId: Long, + val lat: Double, + val lon: Double, + val altitude: Double, + val speed: Double, + val hdop: Double, + val bearing: Double, + val time: Long, + val type: Int) + + data class UserLocations( + var userId: Int, + var chatId: Long, + var botName: String, + var locationsByType: Map> + + + ){ + fun getUniqueSegments(): List { + // TODO TYPE_BOT_MAP. TYPE_BOT_TEXT, TYPE_USER_BOTH, TYPE_BOT_BOTH - delete + val list = mutableListOf() + if(locationsByType.containsKey(TYPE_MY_LOCATION)) { + return locationsByType.get(TYPE_MY_LOCATION)?: list + } + list.addAll(locationsByType.get(TYPE_USER_TEXT)?: emptyList()) + val mapList = locationsByType.get(TYPE_USER_MAP)?: emptyList(); + mapList.forEach { + var ti = 0; + while(ti < list.size && list[ti].maxTime < it.minTime) { + ti++; + } + if(ti < list.size && list[ti].minTime > it.maxTime ) { + list.add(ti, it) + } else if(ti == list.size) { + list.add(it) + } + } + + + return list + } + } + + data class UserTrkSegment( + val points: MutableList, + var distance: Double, + var type: Int, + var minTime: Long, + var maxTime: Long + ) { + fun newer(other: UserTrkSegment): Boolean { + return other.maxTime < maxTime; + } + + + fun overlap(other: UserTrkSegment): Boolean { + + if(other.maxTime < maxTime) { + return other.maxTime > minTime; + } else { + return other.minTime < maxTime; + } + } + + } + + data class LocationHistoryPoint( + val userId: Int, + val chatId: Long, + val type: Int + ) { + + override fun equals(other: Any?): Boolean { + if (other == null) { + return false + } + if (other !is LocationHistoryPoint) { + return false + } + val o = other as LocationHistoryPoint? + return this.userId == o!!.userId && this.chatId == o.chatId && this.type == o.type + } + + override fun hashCode(): Int { + val prime = 31 + var result = 1 + result = prime * result + userId.hashCode() + result = prime * result + chatId.hashCode() + result = prime * result + type.hashCode() + return result + } + } + + + companion object { + + const val TYPE_USER_MAP = 0 + const val TYPE_USER_TEXT = 1 + const val TYPE_USER_BOTH = 2 + const val TYPE_BOT_MAP = 3 + const val TYPE_BOT_TEXT = 4 + const val TYPE_BOT_BOTH = 5 + const val TYPE_MY_LOCATION = 6 + } +} diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/MessagesDbHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/MessagesDbHelper.kt deleted file mode 100644 index dcd4d67c46..0000000000 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/MessagesDbHelper.kt +++ /dev/null @@ -1,122 +0,0 @@ -package net.osmand.telegram.helpers - -import android.content.Context -import android.database.sqlite.SQLiteDatabase -import android.database.sqlite.SQLiteOpenHelper -import net.osmand.telegram.TelegramApplication -import org.drinkless.td.libcore.telegram.TdApi - -class MessagesDbHelper(val app: TelegramApplication) { - - private val messages = HashSet() - - private val sqliteHelper: SQLiteHelper - - init { - sqliteHelper = SQLiteHelper(app) - sqliteHelper.getMessages().forEach { - app.telegramHelper.loadMessage(it.chatId, it.messageId) - } - app.telegramHelper.addIncomingMessagesListener(object : - TelegramHelper.TelegramIncomingMessagesListener { - - override fun onReceiveChatLocationMessages( - chatId: Long, vararg messages: TdApi.Message - ) { - messages.forEach { addMessage(chatId, it.id) } - } - - override fun onDeleteChatLocationMessages(chatId: Long, messages: List) { - messages.forEach { removeMessage(chatId, it.id) } - } - - override fun updateLocationMessages() {} - }) - } - - fun saveMessages() { - clearMessages() - synchronized(messages) { - sqliteHelper.addMessages(messages) - } - } - - fun clearMessages() { - sqliteHelper.clearMessages() - } - - private fun addMessage(chatId: Long, messageId: Long) { - synchronized(messages) { - messages.add(Message(chatId, messageId)) - } - } - - private fun removeMessage(chatId: Long, messageId: Long) { - synchronized(messages) { - messages.remove(Message(chatId, messageId)) - } - } - - private class SQLiteHelper(context: Context) : - SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) { - - override fun onCreate(db: SQLiteDatabase) { - db.execSQL(MESSAGES_TABLE_CREATE) - } - - override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { - db.execSQL(MESSAGES_TABLE_DELETE) - onCreate(db) - } - - internal fun addMessages(messages: Set) { - messages.forEach { - writableDatabase?.execSQL(MESSAGES_TABLE_INSERT, arrayOf(it.chatId, it.messageId)) - } - } - - internal fun getMessages(): Set { - val res = HashSet() - readableDatabase?.rawQuery(MESSAGES_TABLE_SELECT, null)?.apply { - if (moveToFirst()) { - do { - res.add(Message(getLong(0), getLong(1))) - } while (moveToNext()) - } - close() - } - return res - } - - internal fun clearMessages() { - writableDatabase?.execSQL(MESSAGES_TABLE_CLEAR) - } - - companion object { - - private const val DB_NAME = "messages.db" - private const val DB_VERSION = 1 - - private const val MESSAGES_TABLE_NAME = "messages" - private const val MESSAGES_COL_CHAT_ID = "chat_id" - private const val MESSAGES_COL_MESSAGE_ID = "message_id" - - private const val MESSAGES_TABLE_CREATE = - "CREATE TABLE IF NOT EXISTS $MESSAGES_TABLE_NAME (" + - "$MESSAGES_COL_CHAT_ID LONG, " + - "$MESSAGES_COL_MESSAGE_ID LONG)" - - private const val MESSAGES_TABLE_DELETE = "DROP TABLE IF EXISTS $MESSAGES_TABLE_NAME" - - private const val MESSAGES_TABLE_SELECT = - "SELECT $MESSAGES_COL_CHAT_ID, $MESSAGES_COL_MESSAGE_ID FROM $MESSAGES_TABLE_NAME" - - private const val MESSAGES_TABLE_CLEAR = "DELETE FROM $MESSAGES_TABLE_NAME" - - private const val MESSAGES_TABLE_INSERT = "INSERT INTO $MESSAGES_TABLE_NAME (" + - "$MESSAGES_COL_CHAT_ID, $MESSAGES_COL_MESSAGE_ID) VALUES (?, ?)" - } - } - - private data class Message(val chatId: Long, val messageId: Long) -} diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/OsmandAidlHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/OsmandAidlHelper.kt index edac62b427..9608affa9e 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/OsmandAidlHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/OsmandAidlHelper.kt @@ -75,6 +75,12 @@ class OsmandAidlHelper(private val app: TelegramApplication) { fun onSearchComplete(resultSet: List) } + private var gpxBitmapCreatedListener: GpxBitmapCreatedListener? = null + + interface GpxBitmapCreatedListener { + fun onGpxBitmapCreated(bitmap: AGpxBitmap) + } + private val mIOsmAndAidlCallback = object : IOsmAndAidlCallback.Stub() { @Throws(RemoteException::class) @@ -90,12 +96,26 @@ class OsmandAidlHelper(private val app: TelegramApplication) { mUpdatesListener!!.update() } } + + override fun onAppInitialized() { + + } + + override fun onGpxBitmapCreated(bitmap: AGpxBitmap) { + if (gpxBitmapCreatedListener != null) { + gpxBitmapCreatedListener!!.onGpxBitmapCreated(bitmap) + } + } } fun setSearchCompleteListener(mSearchCompleteListener: SearchCompleteListener) { this.mSearchCompleteListener = mSearchCompleteListener } + fun setGpxBitmapCreatedListener(gpxBitmapCreatedListener: GpxBitmapCreatedListener) { + this.gpxBitmapCreatedListener = gpxBitmapCreatedListener + } + private var mUpdatesListener: UpdatesListener? = null interface UpdatesListener { @@ -106,6 +126,7 @@ class OsmandAidlHelper(private val app: TelegramApplication) { this.mUpdatesListener = mUpdatesListener } + fun updatesCallbackRegistered() = osmandCallbackId > 0 /** * Class for interacting with the main interface of the service. */ @@ -183,6 +204,15 @@ class OsmandAidlHelper(private val app: TelegramApplication) { } } + fun execOsmandApi(action: (() -> Unit)) { + if (!isOsmandConnected() && isOsmandBound()) { + connectOsmand() + } + if (isOsmandConnected()) { + action.invoke() + } + } + private fun bindService(packageName: String): Boolean { return if (mIOsmAndAidlInterface == null) { val intent = Intent("net.osmand.aidl.OsmandAidlService") @@ -1052,7 +1082,23 @@ class OsmandAidlHelper(private val app: TelegramApplication) { fun unregisterFromUpdates(): Boolean { if (mIOsmAndAidlInterface != null) { try { - return mIOsmAndAidlInterface!!.unregisterFromUpdates(osmandCallbackId) + val unregistered = mIOsmAndAidlInterface!!.unregisterFromUpdates(osmandCallbackId) + if (unregistered) { + osmandCallbackId = 0 + } + return unregistered + } catch (e: RemoteException) { + e.printStackTrace() + } + } + return false + } + + fun getBitmapForGpx(gpxUri: Uri, density: Float, widthPixels: Int, heightPixels: Int, color: Int): Boolean { + if (mIOsmAndAidlInterface != null) { + try { + app.grantUriPermission(app.settings.appToConnectPackage, gpxUri, Intent.FLAG_GRANT_READ_URI_PERMISSION) + return mIOsmAndAidlInterface!!.getBitmapForGpx(CreateGpxBitmapParams(gpxUri, density, widthPixels, heightPixels, color), mIOsmAndAidlCallback) } catch (e: RemoteException) { e.printStackTrace() } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt index 74001a6d03..74dbec35be 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShareLocationHelper.kt @@ -2,12 +2,19 @@ package net.osmand.telegram.helpers import net.osmand.Location import net.osmand.PlatformUtil -import net.osmand.telegram.TelegramApplication +import net.osmand.telegram.* +import net.osmand.telegram.helpers.LocationMessages.BufferMessage import net.osmand.telegram.notifications.TelegramNotification.NotificationType import net.osmand.telegram.utils.AndroidNetworkUtils +import net.osmand.telegram.utils.BASE_URL +import org.drinkless.td.libcore.telegram.TdApi +import org.json.JSONException +import org.json.JSONObject private const val USER_SET_LIVE_PERIOD_DELAY_MS = 5000 // 5 sec +private const val UPDATE_LOCATION_ACCURACY = 150 // 150 meters + class ShareLocationHelper(private val app: TelegramApplication) { private val log = PlatformUtil.getLog(ShareLocationHelper::class.java) @@ -21,7 +28,7 @@ class ShareLocationHelper(private val app: TelegramApplication) { var distance: Int = 0 private set - var lastLocationMessageSentTime: Long = 0 + var lastLocationUpdateTime: Long = 0 var lastLocation: Location? = null set(value) { @@ -43,19 +50,11 @@ class ShareLocationHelper(private val app: TelegramApplication) { fun updateLocation(location: Location?) { lastLocation = location - if (location != null) { - val chatsShareInfo = app.settings.getChatsShareInfo() - if (chatsShareInfo.isNotEmpty()) { - val user = app.telegramHelper.getCurrentUser() - val sharingMode = app.settings.currentSharingMode - if (user != null && sharingMode == user.id.toString()) { - app.telegramHelper.sendLiveLocationMessage(chatsShareInfo, location.latitude, location.longitude) - } else if (sharingMode.isNotEmpty()) { - val url = "https://live.osmand.net/device/$sharingMode/send?lat=${location.latitude}&lon=${location.longitude}" - AndroidNetworkUtils.sendRequestAsync(url, null) - } + if (location != null && location.accuracy < UPDATE_LOCATION_ACCURACY) { + lastLocationUpdateTime = System.currentTimeMillis() + if (app.settings.getChatsShareInfo().isNotEmpty()) { + shareLocationMessages(location, app.telegramHelper.getCurrentUserId()) } - lastLocationMessageSentTime = System.currentTimeMillis() } app.settings.updateSharingStatusHistory() refreshNotification() @@ -77,14 +76,16 @@ class ShareLocationHelper(private val app: TelegramApplication) { livePeriod } livePeriod = newLivePeriod - shouldDeletePreviousMessage = true + shouldDeletePreviousMapMessage = true + shouldDeletePreviousTextMessage = true currentMessageLimit = currentTime + Math.min(newLivePeriod, TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong()) } } shareInfo.userSetLivePeriod != shareInfo.livePeriod && (shareInfo.userSetLivePeriodStart + USER_SET_LIVE_PERIOD_DELAY_MS) > currentTime -> { shareInfo.apply { - shouldDeletePreviousMessage = true + shouldDeletePreviousMapMessage = true + shouldDeletePreviousTextMessage = true livePeriod = shareInfo.userSetLivePeriod currentMessageLimit = currentTime + Math.min(livePeriod, TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC.toLong()) } @@ -96,6 +97,37 @@ class ShareLocationHelper(private val app: TelegramApplication) { } } + fun checkAndSendBufferMessagesToChat(chatId: Long) { + val shareInfo = app.settings.getChatsShareInfo()[chatId] + if (shareInfo != null && shareInfo.pendingTdLib < 10) { + app.locationMessages.getBufferedMessagesForChat(shareInfo.chatId).forEach { + if (it.type == LocationMessages.TYPE_USER_TEXT && !shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) { + app.telegramHelper.editTextLocation(shareInfo, it) + app.locationMessages.removeBufferedMessage(it) + } else if (it.type == LocationMessages.TYPE_USER_MAP && !shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) { + app.telegramHelper.editMapLocation(shareInfo, it) + app.locationMessages.removeBufferedMessage(it) + } else if (it.type == LocationMessages.TYPE_USER_BOTH) { + var messageSent = false + if (!shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) { + app.telegramHelper.editMapLocation(shareInfo, it) + messageSent = true + } + if (!shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) { + app.telegramHelper.editTextLocation(shareInfo, it) + messageSent = true + } + if (messageSent) { + app.locationMessages.removeBufferedMessage(it) + } + } + if (shareInfo.pendingTdLib >= 10) { + return + } + } + } + } + fun startSharingLocation() { if (!sharingLocation) { sharingLocation = true @@ -103,6 +135,8 @@ class ShareLocationHelper(private val app: TelegramApplication) { app.startMyLocationService() refreshNotification() + + checkAndSendBufferMessages() } else { app.forceUpdateMyLocation() } @@ -132,6 +166,209 @@ class ShareLocationHelper(private val app: TelegramApplication) { refreshNotification() } + private fun checkAndSendBufferMessages(){ + log.debug("checkAndSendBufferMessages") + app.settings.getChatsShareInfo().forEach loop@{ (chatId, shareInfo) -> + if (shareInfo.pendingTdLib < 10) { + app.locationMessages.getBufferedMessagesForChat(chatId).forEach { + if (it.type == LocationMessages.TYPE_USER_TEXT && !shareInfo.pendingTextMessage && shareInfo.currentTextMessageId != -1L) { + app.telegramHelper.editTextLocation(shareInfo, it) + app.locationMessages.removeBufferedMessage(it) + } else if (it.type == LocationMessages.TYPE_USER_MAP && !shareInfo.pendingMapMessage && shareInfo.currentMapMessageId != -1L) { + app.telegramHelper.editMapLocation(shareInfo, it) + app.locationMessages.removeBufferedMessage(it) + } + if (shareInfo.pendingTdLib >= 10) { + return@loop + } + } + } + } + } + + private fun shareLocationMessages(location: Location, userId: Int) { + val chatsShareInfo = app.settings.getChatsShareInfo() + val latitude = location.latitude + val longitude = location.longitude + val altitude = location.altitude + val speed = location.speed.toDouble() + val accuracy = location.accuracy.toDouble() + val bearing = location.bearing.toDouble() + val time = location.time + val sharingMode = app.settings.currentSharingMode + val isBot = sharingMode != userId.toString() + var bufferedMessagesFull = false + val type = when (app.settings.shareTypeValue) { + SHARE_TYPE_MAP -> { + if (isBot) LocationMessages.TYPE_BOT_MAP else LocationMessages.TYPE_USER_MAP + } + SHARE_TYPE_TEXT -> { + if (isBot) LocationMessages.TYPE_BOT_TEXT else LocationMessages.TYPE_USER_TEXT + } + SHARE_TYPE_MAP_AND_TEXT -> { + if (isBot) LocationMessages.TYPE_BOT_BOTH else LocationMessages.TYPE_USER_BOTH + } else -> -1 + } + chatsShareInfo.values.forEach { shareInfo -> + if (shareInfo.pendingTdLib >= 10) { + bufferedMessagesFull = true + } + val message = BufferMessage(shareInfo.chatId, latitude, longitude, altitude, speed, accuracy, bearing, time, type) + + if (type == LocationMessages.TYPE_USER_MAP || type == LocationMessages.TYPE_BOT_MAP) { + prepareMapMessage(shareInfo, message, isBot, sharingMode) + } else if (type == LocationMessages.TYPE_USER_TEXT || type == LocationMessages.TYPE_BOT_TEXT) { + prepareTextMessage(shareInfo, message, isBot, sharingMode) + } else if (type == LocationMessages.TYPE_USER_BOTH || type == LocationMessages.TYPE_BOT_BOTH) { + prepareMapAndTextMessage(shareInfo, message, isBot, sharingMode) + } + } + app.locationMessages.addMyLocationMessage(location) + if (bufferedMessagesFull) { + checkNetworkType() + } + } + + private fun prepareTextMessage(shareInfo: TelegramSettings.ShareChatInfo,message: BufferMessage,isBot:Boolean, sharingMode: String) { + log.debug("prepareTextMessage $message") + if (shareInfo.currentTextMessageId == -1L) { + if (shareInfo.pendingTextMessage) { + app.locationMessages.addBufferedMessage(message) + } else { + if (isBot) { + sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_TEXT) + } else { + app.telegramHelper.sendNewTextLocation(shareInfo, message) + } + } + } else { + if (isBot) { + sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_TEXT) + } else { + if (shareInfo.pendingTdLib < 10) { + app.telegramHelper.editTextLocation(shareInfo, message) + } else { + app.locationMessages.addBufferedMessage(message) + } + } + } + } + + private fun prepareMapMessage(shareInfo: TelegramSettings.ShareChatInfo,message: BufferMessage,isBot:Boolean, sharingMode: String) { + log.debug("prepareMapMessage $message") + if (shareInfo.currentMapMessageId == -1L) { + if (shareInfo.pendingMapMessage) { + app.locationMessages.addBufferedMessage(message) + } else { + if (isBot) { + sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_MAP) + } else { + app.telegramHelper.sendNewMapLocation(shareInfo, message) + } + } + } else { + if (isBot) { + sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_MAP) + } else { + if (shareInfo.pendingTdLib < 10) { + app.telegramHelper.editMapLocation(shareInfo, message) + } else { + app.locationMessages.addBufferedMessage(message) + } + } + } + } + + private fun prepareMapAndTextMessage(shareInfo: TelegramSettings.ShareChatInfo, message: BufferMessage, isBot:Boolean, sharingMode: String) { + log.debug("prepareMapAndTextMessage $message") + if (shareInfo.pendingMapMessage || shareInfo.pendingTextMessage || shareInfo.pendingTdLib >= 10) { + app.locationMessages.addBufferedMessage(message) + } else { + if (isBot) { + sendLocationToBot(message, sharingMode, shareInfo, SHARE_TYPE_MAP_AND_TEXT) + } else { + if (shareInfo.currentMapMessageId == -1L) { + app.telegramHelper.sendNewMapLocation(shareInfo, message) + } else { + app.telegramHelper.editMapLocation(shareInfo, message) + } + if (shareInfo.currentTextMessageId == -1L) { + app.telegramHelper.sendNewTextLocation(shareInfo, message) + } else { + app.telegramHelper.editTextLocation(shareInfo, message) + } + } + } + } + + fun checkNetworkType(){ + if (app.isInternetConnectionAvailable) { + val networkType = when { + app.isWifiConnected -> TdApi.NetworkTypeWiFi() + app.isMobileConnected -> TdApi.NetworkTypeMobile() + else -> TdApi.NetworkTypeOther() + } + app.telegramHelper.networkChange(networkType) + } + } + + private fun sendLocationToBot(locationMessage: BufferMessage, sharingMode: String, shareInfo: TelegramSettings.ShareChatInfo, shareType: String) { + if (app.isInternetConnectionAvailable) { + log.debug("sendLocationToBot $locationMessage") + val url = getDeviceSharingUrl(locationMessage, sharingMode) + AndroidNetworkUtils.sendRequestAsync(app, url, null, "Send Location", false, false, + object : AndroidNetworkUtils.OnRequestResultListener { + override fun onResult(result: String?) { + val chatsShareInfo = app.settings.getChatsShareInfo() + val success = checkResultAndUpdateShareInfoSuccessfulSendTime(result, chatsShareInfo) + val osmandBotId = app.telegramHelper.getOsmandBot()?.id ?: -1 + val device = app.settings.getCurrentSharingDevice() + + if (success && shareInfo.shouldSendViaBotMessage && osmandBotId != -1 && device != null) { + app.telegramHelper.sendViaBotLocationMessage(osmandBotId, shareInfo, TdApi.Location(locationMessage.lat, locationMessage.lon), device, shareType) + shareInfo.shouldSendViaBotMessage = false + } + } + }) + } + } + + private fun getDeviceSharingUrl(loc: BufferMessage, sharingMode: String): String { + val url = "$BASE_URL/device/$sharingMode/send?lat=${loc.lat}&lon=${loc.lon}" + val builder = StringBuilder(url) + if (loc.bearing != 0.0) { + builder.append("&azi=${loc.bearing}") + } + if (loc.speed != 0.0) { + builder.append("&spd=${loc.speed}") + } + if (loc.altitude != 0.0) { + builder.append("&alt=${loc.altitude}") + } + if (loc.hdop != 0.0) { + builder.append("&hdop=${loc.hdop}") + } + return builder.toString() + } + + private fun checkResultAndUpdateShareInfoSuccessfulSendTime(result: String?, chatsShareInfo: Map):Boolean { + if (result != null) { + try { + val jsonResult = JSONObject(result) + val status = jsonResult.getString("status") + val currentTime = System.currentTimeMillis() + if (status == "OK") { + chatsShareInfo.forEach { (_, shareInfo) -> + shareInfo.lastSuccessfulSendTimeMs = currentTime + } + return true + } + } catch (e: JSONException) { + } + } + return false + } + private fun refreshNotification() { app.runInUIThread { app.notificationHelper.refreshNotification(NotificationType.LOCATION) @@ -144,4 +381,4 @@ class ShareLocationHelper(private val app: TelegramApplication) { const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = TelegramHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC - 1 const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = TelegramHelper.MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC + 1 } -} +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt index cb08aafdb1..d36e899557 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/ShowLocationHelper.kt @@ -8,9 +8,11 @@ import net.osmand.aidl.map.ALatLon import net.osmand.aidl.maplayer.point.AMapPoint import net.osmand.telegram.R import net.osmand.telegram.TelegramApplication -import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation import net.osmand.telegram.helpers.TelegramUiHelper.ListItem import net.osmand.telegram.utils.AndroidUtils +import net.osmand.telegram.utils.OsmandLocationUtils +import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation +import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserLocation import org.drinkless.td.libcore.telegram.TdApi import java.io.File import java.util.concurrent.Executors @@ -33,7 +35,7 @@ class ShowLocationHelper(private val app: TelegramApplication) { private var forcedStop: Boolean = false fun setupMapLayer() { - execOsmandApi { + osmandAidlHelper.execOsmandApi { osmandAidlHelper.addMapLayer(MAP_LAYER_ID, "Telegram", 5.5f, null) } } @@ -42,7 +44,7 @@ class ShowLocationHelper(private val app: TelegramApplication) { if (item.latLon == null) { return } - execOsmandApi { + osmandAidlHelper.execOsmandApi { osmandAidlHelper.showMapPoint( MAP_LAYER_ID, item.getMapPointId(), @@ -58,10 +60,10 @@ class ShowLocationHelper(private val app: TelegramApplication) { } fun updateLocationsOnMap() { - execOsmandApi { + osmandAidlHelper.execOsmandApi { val messages = telegramHelper.getMessages() for (message in messages) { - val date = telegramHelper.getLastUpdatedTime(message) + val date = OsmandLocationUtils.getLastUpdatedTime(message) val messageShowingTime = System.currentTimeMillis() / 1000 - date if (messageShowingTime > app.settings.locHistoryTime) { removeMapPoint(message.chatId, message) @@ -73,13 +75,13 @@ class ShowLocationHelper(private val app: TelegramApplication) { } fun addOrUpdateLocationOnMap(message: TdApi.Message, update: Boolean = false) { - execOsmandApi { + osmandAidlHelper.execOsmandApi { val chatId = message.chatId val chatTitle = telegramHelper.getChat(message.chatId)?.title val content = message.content - val date = telegramHelper.getLastUpdatedTime(message) + val date = OsmandLocationUtils.getLastUpdatedTime(message) val stale = System.currentTimeMillis() / 1000 - date > app.settings.staleLocTime - if (chatTitle != null && content is TdApi.MessageLocation) { + if (chatTitle != null && (content is TdApi.MessageLocation || (content is MessageUserLocation && content.isValid()))) { var userName = "" var photoPath: String? = null val user = telegramHelper.getUser(message.senderUserId) @@ -102,12 +104,19 @@ class ShowLocationHelper(private val app: TelegramApplication) { } setupMapLayer() val params = generatePointParams(photoPath, stale) - if (update) { - osmandAidlHelper.updateMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}", userName, userName, - chatTitle, Color.WHITE, ALatLon(content.location.latitude, content.location.longitude), null, params) - } else { - osmandAidlHelper.addMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}", userName, userName, - chatTitle, Color.WHITE, ALatLon(content.location.latitude, content.location.longitude), null, params) + val aLatLon = when (content) { + is TdApi.MessageLocation -> ALatLon(content.location.latitude, content.location.longitude) + is MessageUserLocation -> ALatLon(content.lat, content.lon) + else -> null + } + if (aLatLon != null) { + if (update) { + osmandAidlHelper.updateMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}", userName, userName, + chatTitle, Color.WHITE, aLatLon, null, params) + } else { + osmandAidlHelper.addMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}", userName, userName, + chatTitle, Color.WHITE, aLatLon, null, params) + } } } else if (chatTitle != null && content is MessageOsmAndBotLocation && content.isValid()) { val name = content.name @@ -124,7 +133,7 @@ class ShowLocationHelper(private val app: TelegramApplication) { } fun showChatMessages(chatId: Long) { - execOsmandApi { + osmandAidlHelper.execOsmandApi { val messages = telegramHelper.getChatMessages(chatId) for (message in messages) { addOrUpdateLocationOnMap(message) @@ -137,7 +146,7 @@ class ShowLocationHelper(private val app: TelegramApplication) { } fun hideMessages(messages: List) { - execOsmandApi { + osmandAidlHelper.execOsmandApi { for (message in messages) { val user = telegramHelper.getUser(message.senderUserId) if (user != null) { @@ -149,7 +158,7 @@ class ShowLocationHelper(private val app: TelegramApplication) { fun startShowingLocation() { if (!showingLocation && !forcedStop) { - showingLocation = if (isUseOsmandCallback()) { + showingLocation = if (isUseOsmandCallback() && !app.settings.monitoringEnabled) { osmandAidlHelper.registerForUpdates() } else { app.startUserLocationService() @@ -162,14 +171,32 @@ class ShowLocationHelper(private val app: TelegramApplication) { forcedStop = force if (showingLocation) { showingLocation = false - if (isUseOsmandCallback()) { + if (isUseOsmandCallback() && app.osmandAidlHelper.updatesCallbackRegistered()) { osmandAidlHelper.unregisterFromUpdates() - } else { + } else if (!app.settings.monitoringEnabled) { app.stopUserLocationService() } } } + fun changeUpdatesType() { + if (!forcedStop) { + if (app.settings.monitoringEnabled) { + if (app.osmandAidlHelper.updatesCallbackRegistered()) { + osmandAidlHelper.unregisterFromUpdates() + } + app.startUserLocationService() + } else { + if (isUseOsmandCallback()) { + app.stopUserLocationService() + osmandAidlHelper.registerForUpdates() + } else { + app.startUserLocationService() + } + } + } + } + fun isUseOsmandCallback(): Boolean { val version = AndroidUtils.getAppVersionCode(app, app.settings.appToConnectPackage) return version >= MIN_OSMAND_CALLBACK_VERSION_CODE @@ -242,19 +269,10 @@ class ShowLocationHelper(private val app: TelegramApplication) { private fun removeMapPoint(chatId: Long, message: TdApi.Message) { val content = message.content - if (content is TdApi.MessageLocation) { + if (content is TdApi.MessageLocation || content is MessageUserLocation) { osmandAidlHelper.removeMapPoint(MAP_LAYER_ID, "${chatId}_${message.senderUserId}") } else if (content is MessageOsmAndBotLocation) { osmandAidlHelper.removeMapPoint(MAP_LAYER_ID, "${chatId}_${content.name}") } } - - private fun execOsmandApi(action: (() -> Unit)) { - if (!osmandAidlHelper.isOsmandConnected() && osmandAidlHelper.isOsmandBound()) { - osmandAidlHelper.connectOsmand() - } - if (osmandAidlHelper.isOsmandConnected()) { - action.invoke() - } - } } \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt index cf21651e84..a6e23f6f03 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramHelper.kt @@ -2,16 +2,24 @@ package net.osmand.telegram.helpers import android.text.TextUtils import net.osmand.PlatformUtil -import net.osmand.telegram.TelegramSettings +import net.osmand.telegram.* import net.osmand.telegram.helpers.TelegramHelper.TelegramAuthenticationParameterType.* import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_DIR import net.osmand.telegram.utils.GRAYSCALE_PHOTOS_EXT +import net.osmand.telegram.utils.OsmandLocationUtils +import net.osmand.telegram.utils.OsmandLocationUtils.DEVICE_PREFIX +import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation +import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserLocation +import net.osmand.telegram.utils.OsmandLocationUtils.USER_TEXT_LOCATION_TITLE +import net.osmand.telegram.utils.OsmandLocationUtils.getLastUpdatedTime +import net.osmand.telegram.utils.OsmandLocationUtils.parseOsmAndBotLocation +import net.osmand.telegram.utils.OsmandLocationUtils.parseOsmAndBotLocationContent +import net.osmand.telegram.utils.OsmandLocationUtils.parseTextLocation import org.drinkless.td.libcore.telegram.Client import org.drinkless.td.libcore.telegram.Client.ResultHandler import org.drinkless.td.libcore.telegram.TdApi import org.drinkless.td.libcore.telegram.TdApi.AuthorizationState import java.io.File -import java.text.SimpleDateFormat import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.Executors @@ -30,28 +38,14 @@ class TelegramHelper private constructor() { private const val IGNORED_ERROR_CODE = 406 private const val MESSAGE_CANNOT_BE_EDITED_ERROR_CODE = 5 - private const val DEVICE_PREFIX = "Device: " - private const val LOCATION_PREFIX = "Location: " - private const val LAST_LOCATION_PREFIX = "Last location: " - private const val UPDATED_PREFIX = "Updated: " - - private const val FEW_SECONDS_AGO = "few seconds ago" - private const val SECONDS_AGO_SUFFIX = " seconds ago" - private const val MINUTES_AGO_SUFFIX = " minutes ago" - private const val HOURS_AGO_SUFFIX = " hours ago" - private const val UTC_FORMAT_SUFFIX = " UTC" - - private val UTC_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US).apply { - timeZone = TimeZone.getTimeZone("UTC") - } - - private val UTC_TIME_FORMAT = SimpleDateFormat("HH:mm:ss", Locale.US).apply { - timeZone = TimeZone.getTimeZone("UTC") - } // min and max values for the Telegram API const val MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 61 const val MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC = 60 * 60 * 24 - 1 // one day + const val MAX_LOCATION_MESSAGE_HISTORY_SCAN_SEC = 60 * 60 * 24 // one day + + const val SEND_NEW_MESSAGE_INTERVAL_SEC = 10 * 60 // 10 minutes + private var helper: TelegramHelper? = null val instance: TelegramHelper @@ -65,7 +59,10 @@ class TelegramHelper private constructor() { var messageActiveTimeSec: Long = 0 + var lastTelegramUpdateTime: Int = 0 + private val users = ConcurrentHashMap() + private val contacts = ConcurrentHashMap() private val basicGroups = ConcurrentHashMap() private val supergroups = ConcurrentHashMap() private val secretChats = ConcurrentHashMap() @@ -89,6 +86,7 @@ class TelegramHelper private constructor() { private var client: Client? = null private var currentUser: TdApi.User? = null + private var osmandBot: TdApi.User? = null private var haveFullChatList: Boolean = false private var needRefreshActiveLiveLocationMessages: Boolean = true @@ -138,14 +136,20 @@ class TelegramHelper private constructor() { fun getChatListIds() = getChatList().map { it.chatId } + fun getContacts() = contacts + fun getChatIds() = chats.keys().toList() fun getChat(id: Long) = chats[id] fun getUser(id: Int) = users[id] + fun getOsmandBot() = osmandBot + fun getCurrentUser() = currentUser + fun getCurrentUserId() = currentUser?.id ?: -1 + fun getUserMessage(user: TdApi.User) = usersLocationMessages.values.firstOrNull { it.senderUserId == user.id } @@ -190,17 +194,8 @@ class TelegramHelper private constructor() { return chat.type is TdApi.ChatTypeSupergroup || chat.type is TdApi.ChatTypeBasicGroup } - fun getLastUpdatedTime(message: TdApi.Message): Int { - val content = message.content - return if (content is MessageOsmAndBotLocation) { - content.lastUpdated - } else { - Math.max(message.editDate, message.date) - } - } - fun isPrivateChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypePrivate - + fun isSecretChat(chat: TdApi.Chat): Boolean = chat.type is TdApi.ChatTypeSecret private fun isChannel(chat: TdApi.Chat): Boolean { @@ -232,6 +227,7 @@ class TelegramHelper private constructor() { fun onTelegramChatsRead() fun onTelegramChatsChanged() fun onTelegramChatChanged(chat: TdApi.Chat) + fun onTelegramChatCreated(chat: TdApi.Chat) fun onTelegramUserChanged(user: TdApi.User) fun onTelegramError(code: Int, message: String) } @@ -297,7 +293,7 @@ class TelegramHelper private constructor() { try { log.debug("Loading native tdlib...") System.loadLibrary("tdjni") - Client.setLogVerbosityLevel(0) + Client.setLogVerbosityLevel(1) libraryLoaded = true } catch (e: Throwable) { log.error("Failed to load tdlib", e) @@ -322,6 +318,12 @@ class TelegramHelper private constructor() { } } + fun networkChange(networkType: TdApi.NetworkType) { + client?.send(TdApi.SetNetworkType(networkType)) { obj -> + log.debug(obj) + } + } + fun isInit() = client != null && haveAuthorization fun getUserPhotoPath(user: TdApi.User?) = when { @@ -359,11 +361,20 @@ class TelegramHelper private constructor() { is TdApi.ChatTypeSecret -> type.userId else -> 0 } - + fun isOsmAndBot(userId: Int) = users[userId]?.username == OSMAND_BOT_USERNAME fun isBot(userId: Int) = users[userId]?.type is TdApi.UserTypeBot + fun getSenderMessageId(message: TdApi.Message): Int { + val forwardInfo = message.forwardInfo + return if (forwardInfo != null && forwardInfo is TdApi.MessageForwardedFromUser) { + forwardInfo.senderUserId + } else { + message.senderUserId + } + } + fun startLiveMessagesUpdates(interval: Long) { stopLiveMessagesUpdates() @@ -382,7 +393,12 @@ class TelegramHelper private constructor() { fun hasGrayscaleUserPhoto(userId: Int): Boolean { return File("$appDir/$GRAYSCALE_PHOTOS_DIR$userId$GRAYSCALE_PHOTOS_EXT").exists() } - + + private fun isUserLocationMessage(message: TdApi.Message): Boolean { + val cont = message.content + return (cont is MessageUserLocation || cont is TdApi.MessageLocation) + } + private fun hasLocalUserPhoto(user: TdApi.User): Boolean { val localPhoto = user.profilePhoto?.small?.local return if (localPhoto != null) { @@ -420,7 +436,7 @@ class TelegramHelper private constructor() { } } - private fun requestChats(reload: Boolean = false) { + private fun requestChats(reload: Boolean = false, onComplete: (() -> Unit)?) { synchronized(chatList) { if (reload) { chatList.clear() @@ -451,7 +467,8 @@ class TelegramHelper private constructor() { } } // chats had already been received through updates, let's retry request - requestChats() + requestChats(false, this@TelegramHelper::scanChatsHistory) + onComplete?.invoke() } else -> listener?.onTelegramError(-1, "Receive wrong response from TDLib: $obj") } @@ -480,6 +497,54 @@ class TelegramHelper private constructor() { } } + fun sendViaBotLocationMessage(userId: Int, shareInfo: TelegramSettings.ShareChatInfo, location: TdApi.Location, device: TelegramSettings.DeviceBot, shareType:String) { + log.debug("sendViaBotLocationMessage - ${shareInfo.chatId}") + client?.send(TdApi.GetInlineQueryResults(userId, shareInfo.chatId, location, device.deviceName, "")) { obj -> + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + listener?.onTelegramError(error.code, error.message) + } else { + shareInfo.shouldSendViaBotMessage = true + } + } + TdApi.InlineQueryResults.CONSTRUCTOR -> { + sendViaBotMessageFromQueryResults(shareInfo, obj as TdApi.InlineQueryResults, device.externalId, shareType) + } + } + } + } + + private fun sendViaBotMessageFromQueryResults( + shareInfo: TelegramSettings.ShareChatInfo, + inlineQueryResults: TdApi.InlineQueryResults, + deviceId: String, + shareType: String + ) { + val queryResults = inlineQueryResults.results.asList() + if (queryResults.isNotEmpty()) { + val resultArticles = mutableListOf() + queryResults.forEach { + if (it is TdApi.InlineQueryResultArticle && it.id.substring(1) == deviceId) { + val textLocationArticle = it.id.startsWith("t") + val mapLocationArticle = it.id.startsWith("m") + if (shareType == SHARE_TYPE_MAP && mapLocationArticle + || shareType == SHARE_TYPE_TEXT && textLocationArticle + || shareType == SHARE_TYPE_MAP_AND_TEXT && (textLocationArticle || mapLocationArticle)) { + resultArticles.add(it) + } + } + } + resultArticles.forEach { + client?.send(TdApi.SendInlineQueryResultMessage(shareInfo.chatId, 0, true, + true, inlineQueryResults.inlineQueryId, it.id)) { obj -> + handleTextLocationMessageUpdate(obj, shareInfo) + } + } + } + } + private fun requestSupergroupFullInfo(id: Int) { client?.send(TdApi.GetSupergroupFullInfo(id)) { obj -> when (obj.constructor) { @@ -518,8 +583,109 @@ class TelegramHelper private constructor() { } } - fun loadMessage(chatId: Long, messageId: Long) { - requestMessage(chatId, messageId, this@TelegramHelper::addNewMessage) + private fun requestContacts(){ + client?.send(TdApi.GetContacts()) { obj -> + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + listener?.onTelegramError(error.code, error.message) + } + } + TdApi.Users.CONSTRUCTOR -> { + val usersIds = obj as TdApi.Users + usersIds.userIds.forEach { + requestUser(it) + } + } + } + } + } + + fun scanChatsHistory() { + log.debug("scanChatsHistory: chatList: ${chatList.size}") + chatList.forEach { + scanChatHistory(it.chatId, 0, 0, 100) + } + } + + private fun scanChatHistory( + chatId: Long, + fromMessageId: Long, + offset: Int, + limit: Int, + onlyLocal: Boolean = false + ) { + client?.send(TdApi.GetChatHistory(chatId, fromMessageId, offset, limit, onlyLocal)) { obj -> + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + listener?.onTelegramError(error.code, error.message) + } + } + TdApi.Messages.CONSTRUCTOR -> { + val messages = (obj as TdApi.Messages).messages + log.debug("scanChatHistory: chatId: $chatId fromMessageId: $fromMessageId size: ${messages.size}") + if (messages.isNotEmpty()) { + messages.forEach { + addNewMessage(it) + } + val lastMessage = messages.last() + val currentTime = System.currentTimeMillis() / 1000 + if (currentTime-Math.max(lastMessage.date, lastMessage.editDate) < MAX_LOCATION_MESSAGE_HISTORY_SCAN_SEC) { + scanChatHistory(chatId, lastMessage.id, 0, 100) + log.debug("scanChatHistory searchMessageId: ${lastMessage.id}") + } else { + log.debug("scanChatHistory finishForChat: $chatId") + } + } + } + } + } + } + + private fun requestUser(id: Int) { + client?.send(TdApi.GetUser(id)) { obj -> + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + listener?.onTelegramError(error.code, error.message) + } + } + TdApi.User.CONSTRUCTOR -> { + val user = obj as TdApi.User + contacts[user.id] = user + if (!hasLocalUserPhoto(user) && hasRemoteUserPhoto(user)) { + requestUserPhoto(user) + } + } + } + } + } + + fun createPrivateChatWithUser( + userId: Int, + shareInfo: TelegramSettings.ShareChatInfo, + shareChatsInfo: ConcurrentHashMap + ) { + client?.send(TdApi.CreatePrivateChat(userId, false)) { obj -> + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + shareInfo.hasSharingError = true + listener?.onTelegramError(error.code, error.message) + } + } + TdApi.Chat.CONSTRUCTOR -> { + shareInfo.chatId = (obj as TdApi.Chat).id + shareChatsInfo[shareInfo.chatId] = shareInfo + listener?.onTelegramChatCreated(obj) + } + } + } } private fun requestMessage(chatId: Long, messageId: Long, onComplete: (TdApi.Message) -> Unit) { @@ -531,19 +697,34 @@ class TelegramHelper private constructor() { } private fun addNewMessage(message: TdApi.Message) { + lastTelegramUpdateTime = Math.max(lastTelegramUpdateTime, Math.max(message.date, message.editDate)) if (message.isAppropriate()) { + if (message.isOutgoing) { + return + } + log.debug("addNewMessage: ${message.id}") val fromBot = isOsmAndBot(message.senderUserId) val viaBot = isOsmAndBot(message.viaBotUserId) val oldContent = message.content if (oldContent is TdApi.MessageText) { - message.content = parseOsmAndBotLocation(oldContent.text.text) + if (oldContent.text.text.startsWith(DEVICE_PREFIX)) { + message.content = parseTextLocation(oldContent.text) + } else if (oldContent.text.text.startsWith(USER_TEXT_LOCATION_TITLE)) { + message.content = parseTextLocation(oldContent.text, false) + } } else if (oldContent is TdApi.MessageLocation && (fromBot || viaBot)) { message.content = parseOsmAndBotLocation(message) } removeOldMessages(message, fromBot, viaBot) - usersLocationMessages[message.id] = message + val oldMessage = usersLocationMessages.values.firstOrNull { getSenderMessageId(it) == getSenderMessageId(message) && !fromBot && !viaBot } + val hasNewerMessage = oldMessage != null && (Math.max(message.editDate, message.date) < Math.max(oldMessage.editDate, oldMessage.date)) + if (!hasNewerMessage) { + usersLocationMessages[message.id] = message + } incomingMessagesListeners.forEach { - it.onReceiveChatLocationMessages(message.chatId, message) + if (!hasNewerMessage || it is TelegramService) { + it.onReceiveChatLocationMessages(message.chatId, message) + } } } } @@ -553,7 +734,7 @@ class TelegramHelper private constructor() { while (iterator.hasNext()) { val message = iterator.next().value if (newMessage.chatId == message.chatId) { - val sameSender = newMessage.senderUserId == message.senderUserId + val sameSender = getSenderMessageId(newMessage) == getSenderMessageId(message) val viaSameBot = newMessage.viaBotUserId == message.viaBotUserId if (fromBot || viaBot) { if ((fromBot && sameSender) || (viaBot && viaSameBot)) { @@ -565,39 +746,19 @@ class TelegramHelper private constructor() { } } } - } else if (sameSender) { + } else if (sameSender && isUserLocationMessage(message) && isUserLocationMessage(newMessage) + && Math.max(newMessage.editDate, newMessage.date) >= Math.max(message.editDate, message.date)) { iterator.remove() } } } } - /** - * @chatId Id of the chat - * @livePeriod Period for which the location can be updated, in seconds; should be between 60 and 86400 for a live location and 0 otherwise. - * @latitude Latitude of the location - * @longitude Longitude of the location - */ - fun sendLiveLocationMessage(chatsShareInfo:Map, latitude: Double, longitude: Double): Boolean { - if (!requestingActiveLiveLocationMessages && haveAuthorization) { - if (needRefreshActiveLiveLocationMessages) { - getActiveLiveLocationMessages { - sendLiveLocationImpl(chatsShareInfo, latitude, longitude) - } - needRefreshActiveLiveLocationMessages = false - } else { - sendLiveLocationImpl(chatsShareInfo, latitude, longitude) - } - return true - } - return false - } - fun stopSendingLiveLocationToChat(shareInfo: TelegramSettings.ShareChatInfo) { - if (shareInfo.currentMessageId != -1L && shareInfo.chatId != -1L) { + if (shareInfo.currentMapMessageId != -1L && shareInfo.chatId != -1L) { client?.send( - TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMessageId, null, null)) { obj -> - handleLiveLocationMessageUpdate(obj, shareInfo) + TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMapMessageId, null, null)) { obj -> + handleMapLocationMessageUpdate(obj, shareInfo) } } needRefreshActiveLiveLocationMessages = true @@ -608,7 +769,7 @@ class TelegramHelper private constructor() { stopSendingLiveLocationToChat(chatInfo) } } - + fun getActiveLiveLocationMessages(onComplete: (() -> Unit)?) { requestingActiveLiveLocationMessages = true client?.send(TdApi.GetActiveLiveLocationMessages()) { obj -> @@ -625,6 +786,7 @@ class TelegramHelper private constructor() { TdApi.Messages.CONSTRUCTOR -> { val messages = (obj as TdApi.Messages).messages if (messages.isNotEmpty()) { + log.debug("getActiveLiveLocationMessages: $messages") outgoingMessagesListeners.forEach { it.onUpdateMessages(messages.asList()) } @@ -639,20 +801,26 @@ class TelegramHelper private constructor() { } } - private fun recreateLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageLocation) { - if (shareInfo.currentMessageId != -1L && shareInfo.chatId != -1L) { - log.info("recreateLiveLocationMessage - $shareInfo.currentMessageId") + private fun recreateLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent) { + if (shareInfo.chatId != -1L) { val array = LongArray(1) - array[0] = shareInfo.currentMessageId - client?.send(TdApi.DeleteMessages(shareInfo.chatId, array, true)) { obj -> - when (obj.constructor) { - TdApi.Ok.CONSTRUCTOR -> sendNewLiveLocationMessage(shareInfo, content) - TdApi.Error.CONSTRUCTOR -> { - val error = obj as TdApi.Error - if (error.code != IGNORED_ERROR_CODE) { - needRefreshActiveLiveLocationMessages = true - outgoingMessagesListeners.forEach { - it.onSendLiveLocationError(error.code, error.message) + if (content is TdApi.InputMessageLocation) { + array[0] = shareInfo.currentMapMessageId + } else if (content is TdApi.InputMessageText) { + array[0] = shareInfo.currentTextMessageId + } + if (array[0] != 0L) { + log.debug("recreateLiveLocationMessage - ${array[0]}") + client?.send(TdApi.DeleteMessages(shareInfo.chatId, array, true)) { obj -> + when (obj.constructor) { + TdApi.Ok.CONSTRUCTOR -> sendNewLiveLocationMessage(shareInfo, content) + TdApi.Error.CONSTRUCTOR -> { + val error = obj as TdApi.Error + if (error.code != IGNORED_ERROR_CODE) { + needRefreshActiveLiveLocationMessages = true + outgoingMessagesListeners.forEach { + it.onSendLiveLocationError(error.code, error.message) + } } } } @@ -662,54 +830,82 @@ class TelegramHelper private constructor() { needRefreshActiveLiveLocationMessages = true } - private fun sendNewLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageLocation) { + private fun sendNewLiveLocationMessage(shareInfo: TelegramSettings.ShareChatInfo, content: TdApi.InputMessageContent) { needRefreshActiveLiveLocationMessages = true - log.info("sendNewLiveLocationMessage") - client?.send( - TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj -> - handleLiveLocationMessageUpdate(obj, shareInfo) + log.debug("sendNewLiveLocationMessage") + client?.send(TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj -> + if (content is TdApi.InputMessageText) { + handleTextLocationMessageUpdate(obj, shareInfo) + } else if (content is TdApi.InputMessageLocation) { + handleMapLocationMessageUpdate(obj, shareInfo) + } } } - private fun sendLiveLocationImpl(chatsShareInfo: Map, latitude: Double, longitude: Double) { - val location = TdApi.Location(latitude, longitude) - chatsShareInfo.forEach { (chatId, shareInfo) -> - if (shareInfo.getChatLiveMessageExpireTime() <= 0) { - return@forEach + fun sendNewTextLocation(shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.BufferMessage) { + shareInfo.updateTextMessageId = 1 + val content = OsmandLocationUtils.getTextMessageContent(shareInfo.updateTextMessageId, location) + if (!shareInfo.pendingTextMessage) { + shareInfo.pendingTextMessage = true + shareInfo.pendingTdLib++ + log.error("sendNewTextLocation ${shareInfo.pendingTdLib}") + client?.send(TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj -> + handleTextLocationMessageUpdate(obj, shareInfo) } - val livePeriod = - if (shareInfo.currentMessageLimit > (shareInfo.start + MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC)) { - MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC - } else { - shareInfo.livePeriod.toInt() - } - val content = TdApi.InputMessageLocation(location, livePeriod) - val msgId = shareInfo.currentMessageId - if (msgId != -1L) { - if (shareInfo.shouldDeletePreviousMessage) { - recreateLiveLocationMessage(shareInfo, content) - shareInfo.shouldDeletePreviousMessage = false - shareInfo.currentMessageId = -1 - } else { - log.info("EditMessageLiveLocation - $msgId") - client?.send( - TdApi.EditMessageLiveLocation(chatId, msgId, null, location)) { obj -> - handleLiveLocationMessageUpdate(obj, shareInfo) - } - } + } + } + + fun editTextLocation(shareInfo: TelegramSettings.ShareChatInfo, location: LocationMessages.BufferMessage) { + val content = OsmandLocationUtils.getTextMessageContent(shareInfo.updateTextMessageId, location) + if (shareInfo.currentTextMessageId!=-1L) { + shareInfo.pendingTdLib++ + log.info("editTextLocation ${shareInfo.currentTextMessageId} pendingTdLib: ${shareInfo.pendingTdLib}") + client?.send(TdApi.EditMessageText(shareInfo.chatId, shareInfo.currentTextMessageId, null, content)) { obj -> + handleTextLocationMessageUpdate(obj, shareInfo) + } + } + } + + fun sendNewMapLocation(shareInfo: TelegramSettings.ShareChatInfo, locationMessage: LocationMessages.BufferMessage) { + needRefreshActiveLiveLocationMessages = true + val location = TdApi.Location(locationMessage.lat, locationMessage.lon) + val livePeriod = + if (shareInfo.currentMessageLimit > (shareInfo.start + MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC)) { + MAX_LOCATION_MESSAGE_LIVE_PERIOD_SEC } else { - sendNewLiveLocationMessage(shareInfo, content) + shareInfo.livePeriod.toInt() + } + val content = TdApi.InputMessageLocation(location, livePeriod) + if (!shareInfo.pendingMapMessage) { + shareInfo.pendingMapMessage = true + shareInfo.pendingTdLib++ + log.error("sendNewMapLocation ${shareInfo.pendingTdLib}") + client?.send(TdApi.SendMessage(shareInfo.chatId, 0, false, true, null, content)) { obj -> + handleMapLocationMessageUpdate(obj, shareInfo) } } } - private fun handleLiveLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) { + fun editMapLocation(shareInfo: TelegramSettings.ShareChatInfo, locationMessage: LocationMessages.BufferMessage) { + needRefreshActiveLiveLocationMessages = true + val location = TdApi.Location(locationMessage.lat, locationMessage.lon) + if (shareInfo.currentMapMessageId!=-1L) { + shareInfo.pendingTdLib++ + log.info("editMapLocation ${shareInfo.currentMapMessageId} pendingTdLib: ${shareInfo.pendingTdLib}") + client?.send(TdApi.EditMessageLiveLocation(shareInfo.chatId, shareInfo.currentMapMessageId, null, location)) { obj -> + handleMapLocationMessageUpdate(obj, shareInfo) + } + } + } + + private fun handleMapLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) { when (obj.constructor) { TdApi.Error.CONSTRUCTOR -> { + log.debug("handleMapLocationMessageUpdate - ERROR $obj") val error = obj as TdApi.Error needRefreshActiveLiveLocationMessages = true if (error.code == MESSAGE_CANNOT_BE_EDITED_ERROR_CODE) { - shareInfo.shouldDeletePreviousMessage = true + shareInfo.shouldDeletePreviousMapMessage = true } else if (error.code != IGNORED_ERROR_CODE) { shareInfo.hasSharingError = true outgoingMessagesListeners.forEach { @@ -719,16 +915,31 @@ class TelegramHelper private constructor() { } TdApi.Message.CONSTRUCTOR -> { if (obj is TdApi.Message) { - if (obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR) { - shareInfo.hasSharingError = true - needRefreshActiveLiveLocationMessages = true - outgoingMessagesListeners.forEach { - it.onSendLiveLocationError(-1, "Live location message ${obj.id} failed to send") + when { + obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> { + shareInfo.hasSharingError = true + needRefreshActiveLiveLocationMessages = true + shareInfo.pendingMapMessage = false + log.debug("handleTextLocationMessageUpdate - MessageSendingStateFailed") + outgoingMessagesListeners.forEach { + it.onSendLiveLocationError(-1, "Map location message ${obj.id} failed to send") + } } - } else { - shareInfo.hasSharingError = false - outgoingMessagesListeners.forEach { - it.onUpdateMessages(listOf(obj)) + obj.sendingState?.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR -> { + shareInfo.pendingMapMessage = true + shareInfo.lastSendMapMessageTime = obj.date + log.debug("handleMapLocationMessageUpdate - MessageSendingStatePending") + outgoingMessagesListeners.forEach { + it.onUpdateMessages(listOf(obj)) + } + } + else -> { + shareInfo.hasSharingError = false + shareInfo.pendingMapMessage = false + log.debug("handleMapLocationMessageUpdate - MessageSendingStateSuccess") + outgoingMessagesListeners.forEach { + it.onUpdateMessages(listOf(obj)) + } } } } @@ -736,21 +947,52 @@ class TelegramHelper private constructor() { } } - /** - * @chatId Id of the chat - * @message Text of the message - */ - fun sendTextMessage(chatId: Long, message: String): Boolean { - // initialize reply markup just for testing - //val row = arrayOf(TdApi.InlineKeyboardButton("https://telegram.org?1", TdApi.InlineKeyboardButtonTypeUrl()), TdApi.InlineKeyboardButton("https://telegram.org?2", TdApi.InlineKeyboardButtonTypeUrl()), TdApi.InlineKeyboardButton("https://telegram.org?3", TdApi.InlineKeyboardButtonTypeUrl())) - //val replyMarkup = TdApi.ReplyMarkupInlineKeyboard(arrayOf(row, row, row)) - - if (haveAuthorization) { - val content = TdApi.InputMessageText(TdApi.FormattedText(message, null), false, true) - client?.send(TdApi.SendMessage(chatId, 0, false, true, null, content), defaultHandler) - return true + private fun handleTextLocationMessageUpdate(obj: TdApi.Object, shareInfo: TelegramSettings.ShareChatInfo) { + when (obj.constructor) { + TdApi.Error.CONSTRUCTOR -> { + log.debug("handleTextLocationMessageUpdate - ERROR") + val error = obj as TdApi.Error + if (error.code == MESSAGE_CANNOT_BE_EDITED_ERROR_CODE) { + shareInfo.shouldDeletePreviousTextMessage = true + } else if (error.code != IGNORED_ERROR_CODE) { + shareInfo.hasSharingError = true + outgoingMessagesListeners.forEach { + it.onSendLiveLocationError(error.code, error.message) + } + } + } + TdApi.Message.CONSTRUCTOR -> { + if (obj is TdApi.Message) { + when { + obj.sendingState?.constructor == TdApi.MessageSendingStateFailed.CONSTRUCTOR -> { + shareInfo.hasSharingError = true + shareInfo.pendingTextMessage = false + needRefreshActiveLiveLocationMessages = true + log.debug("handleTextLocationMessageUpdate - MessageSendingStateFailed") + outgoingMessagesListeners.forEach { + it.onSendLiveLocationError(-1, "Text location message ${obj.id} failed to send") + } + } + obj.sendingState?.constructor == TdApi.MessageSendingStatePending.CONSTRUCTOR -> { + shareInfo.pendingTextMessage = true + shareInfo.lastSendTextMessageTime = obj.date + log.debug("handleTextLocationMessageUpdate - MessageSendingStatePending") + outgoingMessagesListeners.forEach { + it.onUpdateMessages(listOf(obj)) + } + } + else -> { + shareInfo.hasSharingError = false + shareInfo.pendingTextMessage = false + log.debug("handleTextLocationMessageUpdate - MessageSendingStateSuccess") + outgoingMessagesListeners.forEach { + it.onUpdateMessages(listOf(obj)) + } + } + } + } + } } - return false } fun logout(): Boolean { @@ -850,8 +1092,9 @@ class TelegramHelper private constructor() { if (wasAuthorized != haveAuthorization) { needRefreshActiveLiveLocationMessages = true if (haveAuthorization) { - requestChats(true) + requestChats(true, null) requestCurrentUser() + requestContacts() } } val newAuthState = getTelegramAuthorizationState() @@ -862,142 +1105,24 @@ class TelegramHelper private constructor() { if (isChannelPost) { return false } + val content = content + val isUserTextLocation = (content is TdApi.MessageText) && content.text.text.startsWith(USER_TEXT_LOCATION_TITLE) val isOsmAndBot = isOsmAndBot(senderUserId) || isOsmAndBot(viaBotUserId) - if (isOutgoing && !isOsmAndBot) { + if (!(isUserTextLocation || content is TdApi.MessageLocation || isOsmAndBot)) { return false } val lastEdited = Math.max(date, editDate) if (TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) - lastEdited > messageActiveTimeSec) { return false } - val content = content + return when (content) { is TdApi.MessageLocation -> true - is TdApi.MessageText -> (isOsmAndBot) && content.text.text.startsWith(DEVICE_PREFIX) + is TdApi.MessageText -> (isOsmAndBot) && content.text.text.startsWith(DEVICE_PREFIX) || isUserTextLocation else -> false } } - private fun parseOsmAndBotLocation(message: TdApi.Message): MessageOsmAndBotLocation { - val messageLocation = message.content as TdApi.MessageLocation - return MessageOsmAndBotLocation().apply { - name = getOsmAndBotDeviceName(message) - lat = messageLocation.location.latitude - lon = messageLocation.location.longitude - lastUpdated = getLastUpdatedTime(message) - } - } - - private fun parseOsmAndBotLocationContent(oldContent:MessageOsmAndBotLocation, content: TdApi.MessageContent): MessageOsmAndBotLocation { - val messageLocation = content as TdApi.MessageLocation - return MessageOsmAndBotLocation().apply { - name = oldContent.name - lat = messageLocation.location.latitude - lon = messageLocation.location.longitude - lastUpdated = (System.currentTimeMillis() / 1000).toInt() - } - } - - private fun parseOsmAndBotLocation(text: String): MessageOsmAndBotLocation { - val res = MessageOsmAndBotLocation() - var locationNA = false - for (s in text.lines()) { - when { - s.startsWith(DEVICE_PREFIX) -> { - res.name = s.removePrefix(DEVICE_PREFIX) - } - s.startsWith(LOCATION_PREFIX) || s.startsWith(LAST_LOCATION_PREFIX) -> { - var locStr: String - var parse = true - if (s.startsWith(LAST_LOCATION_PREFIX)) { - locStr = s.removePrefix(LAST_LOCATION_PREFIX) - if (!locationNA) { - parse = false - } - } else { - locStr = s.removePrefix(LOCATION_PREFIX) - if (locStr.trim() == "n/a") { - locationNA = true - parse = false - } - } - if (parse) { - try { - val (latS, lonS) = locStr.split(" ") - val updatedS = locStr.substring(locStr.indexOf("("), locStr.length) - - res.lastUpdated = - (parseTime(updatedS.removePrefix("(").removeSuffix(")")) / 1000).toInt() - res.lat = latS.dropLast(1).toDouble() - res.lon = lonS.toDouble() - - } catch (e: Exception) { - e.printStackTrace() - } - } - } - s.startsWith(UPDATED_PREFIX) -> { - if (res.lastUpdated == 0) { - val updatedStr = s.removePrefix(UPDATED_PREFIX) - val updatedS = updatedStr.substring(0, updatedStr.indexOf("(")) - res.lastUpdated = (parseTime(updatedS.trim()) / 1000).toInt() - } - } - } - } - return res - } - - private fun parseTime(timeS: String): Long { - try { - when { - timeS.endsWith(FEW_SECONDS_AGO) -> return System.currentTimeMillis() - 5000 - - timeS.endsWith(SECONDS_AGO_SUFFIX) -> { - val locStr = timeS.removeSuffix(SECONDS_AGO_SUFFIX) - return System.currentTimeMillis() - locStr.toLong() * 1000 - } - timeS.endsWith(MINUTES_AGO_SUFFIX) -> { - val locStr = timeS.removeSuffix(MINUTES_AGO_SUFFIX) - val minutes = locStr.toLong() - return System.currentTimeMillis() - minutes * 60 * 1000 - } - timeS.endsWith(HOURS_AGO_SUFFIX) -> { - val locStr = timeS.removeSuffix(HOURS_AGO_SUFFIX) - val hours = locStr.toLong() - return (System.currentTimeMillis() - hours * 60 * 60 * 1000) - } - timeS.endsWith(UTC_FORMAT_SUFFIX) -> { - val locStr = timeS.removeSuffix(UTC_FORMAT_SUFFIX) - val (latS, lonS) = locStr.split(" ") - val date = UTC_DATE_FORMAT.parse(latS) - val time = UTC_TIME_FORMAT.parse(lonS) - val res = date.time + time.time - return res - } - } - } catch (e: Exception) { - e.printStackTrace() - } - return 0 - } - - class MessageOsmAndBotLocation : TdApi.MessageContent() { - - var name: String = "" - internal set - var lat: Double = Double.NaN - internal set - var lon: Double = Double.NaN - internal set - var lastUpdated: Int = 0 - internal set - - override fun getConstructor() = -1 - - fun isValid() = name != "" && lat != Double.NaN && lon != Double.NaN - } - class OrderedChat internal constructor(internal val order: Long, internal val chatId: Long, internal val isChannel: Boolean) : Comparable { override fun compareTo(other: OrderedChat): Int { @@ -1036,7 +1161,14 @@ class TelegramHelper private constructor() { TdApi.UpdateUser.CONSTRUCTOR -> { val updateUser = obj as TdApi.UpdateUser - users[updateUser.user.id] = updateUser.user + val user = updateUser.user + users[updateUser.user.id] = user + if (user.outgoingLink is TdApi.LinkStateIsContact) { + contacts[user.id] = user + } + if (isOsmAndBot(user.id)) { + osmandBot = user + } } TdApi.UpdateUserStatus.CONSTRUCTOR -> { val updateUserStatus = obj as TdApi.UpdateUserStatus @@ -1179,35 +1311,30 @@ class TelegramHelper private constructor() { } TdApi.UpdateMessageEdited.CONSTRUCTOR -> { val updateMessageEdited = obj as TdApi.UpdateMessageEdited - val message = usersLocationMessages[updateMessageEdited.messageId] - if (message == null) { - updateMessageEdited.apply { - requestMessage(chatId, messageId, this@TelegramHelper::addNewMessage) - } - } else { - synchronized(message) { - message.editDate = updateMessageEdited.editDate - } - incomingMessagesListeners.forEach { - it.onReceiveChatLocationMessages(message.chatId, message) - } - } + lastTelegramUpdateTime = Math.max(lastTelegramUpdateTime, updateMessageEdited.editDate) } TdApi.UpdateMessageContent.CONSTRUCTOR -> { val updateMessageContent = obj as TdApi.UpdateMessageContent val message = usersLocationMessages[updateMessageContent.messageId] + log.debug("UpdateMessageContent " + updateMessageContent.messageId) if (message == null) { updateMessageContent.apply { requestMessage(chatId, messageId, this@TelegramHelper::addNewMessage) } } else { synchronized(message) { + lastTelegramUpdateTime = Math.max(lastTelegramUpdateTime, Math.max(message.date, message.editDate)) val newContent = updateMessageContent.newContent + val fromBot = isOsmAndBot(message.senderUserId) + val viaBot = isOsmAndBot(message.viaBotUserId) message.content = if (newContent is TdApi.MessageText) { - parseOsmAndBotLocation(newContent.text.text) - } else if (newContent is TdApi.MessageLocation && - (isOsmAndBot(message.senderUserId) || isOsmAndBot(message.viaBotUserId))) { - parseOsmAndBotLocationContent(message.content as MessageOsmAndBotLocation, newContent) + parseTextLocation(newContent.text, (fromBot || viaBot)) + } else if (newContent is TdApi.MessageLocation) { + if(fromBot||viaBot){ + parseOsmAndBotLocationContent(message.content as MessageOsmAndBotLocation, newContent) + } else { + OsmandLocationUtils.parseUserMapLocation(message) + } } else { newContent } @@ -1219,6 +1346,7 @@ class TelegramHelper private constructor() { } TdApi.UpdateNewMessage.CONSTRUCTOR -> { addNewMessage((obj as TdApi.UpdateNewMessage).message) + log.debug("UpdateNewMessage " + obj.message.id) } TdApi.UpdateMessageMentionRead.CONSTRUCTOR -> { val updateChat = obj as TdApi.UpdateMessageMentionRead @@ -1324,6 +1452,14 @@ class TelegramHelper private constructor() { fullInfoUpdatesListeners.forEach { it.onSupergroupFullInfoUpdated(id, info) } } } + TdApi.UpdateMessageSendSucceeded.CONSTRUCTOR -> { + val updateSucceeded = obj as TdApi.UpdateMessageSendSucceeded + val message = updateSucceeded.message + log.debug("UpdateMessageSendSucceeded: ${message.id} oldId: ${updateSucceeded.oldMessageId}") + outgoingMessagesListeners.forEach { + it.onUpdateMessages(listOf(message)) + } + } } } } @@ -1345,4 +1481,4 @@ class TelegramHelper private constructor() { }// result is already received through UpdateAuthorizationState, nothing to do } } -} +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt index e9b11f081d..4b846dc88d 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/helpers/TelegramUiHelper.kt @@ -6,7 +6,10 @@ import android.widget.ImageView import net.osmand.data.LatLon import net.osmand.telegram.R import net.osmand.telegram.TelegramApplication -import net.osmand.telegram.helpers.TelegramHelper.MessageOsmAndBotLocation +import net.osmand.telegram.utils.GPXUtilities.GPXFile +import net.osmand.telegram.utils.OsmandLocationUtils +import net.osmand.telegram.utils.OsmandLocationUtils.MessageOsmAndBotLocation +import net.osmand.telegram.utils.OsmandLocationUtils.MessageUserLocation import org.drinkless.td.libcore.telegram.TdApi object TelegramUiHelper { @@ -63,10 +66,12 @@ object TelegramUiHelper { val user = helper.getUser(userId) val message = messages.firstOrNull { it.viaBotUserId == 0 } if (message != null) { - res.lastUpdated = helper.getLastUpdatedTime(message) + res.lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message) val content = message.content if (content is TdApi.MessageLocation) { res.latLon = LatLon(content.location.latitude, content.location.longitude) + } else if (content is MessageUserLocation) { + res.latLon = LatLon(content.lat, content.lon) } } if (user != null) { @@ -106,6 +111,7 @@ object TelegramUiHelper { val content = message.content return when (content) { is MessageOsmAndBotLocation -> botMessageToLocationItem(chat, content) + is MessageUserLocation -> locationMessageToLocationItem(helper, chat, message) is TdApi.MessageLocation -> locationMessageToLocationItem(helper, chat, message) else -> null } @@ -120,6 +126,7 @@ object TelegramUiHelper { return when (content) { is MessageOsmAndBotLocation -> botMessageToChatItem(helper, chat, content) is TdApi.MessageLocation -> locationMessageToChatItem(helper, chat, message) + is MessageUserLocation -> locationMessageToChatItem(helper, chat, message) else -> null } } @@ -148,17 +155,21 @@ object TelegramUiHelper { message: TdApi.Message ): LocationItem? { val user = helper.getUser(message.senderUserId) ?: return null - val content = message.content as TdApi.MessageLocation + val content = message.content return LocationItem().apply { chatId = chat.id chatTitle = chat.title name = TelegramUiHelper.getUserName(user) - latLon = LatLon(content.location.latitude, content.location.longitude) + latLon = when (content) { + is TdApi.MessageLocation -> LatLon(content.location.latitude, content.location.longitude) + is MessageUserLocation -> LatLon(content.lat, content.lon) + else -> null + } photoPath = helper.getUserPhotoPath(user) grayscalePhotoPath = helper.getUserGreyPhotoPath(user) placeholderId = R.drawable.img_user_picture userId = message.senderUserId - lastUpdated = helper.getLastUpdatedTime(message) + lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message) } } @@ -190,12 +201,16 @@ object TelegramUiHelper { message: TdApi.Message ): ChatItem? { val user = helper.getUser(message.senderUserId) ?: return null - val content = message.content as TdApi.MessageLocation + val content = message.content return ChatItem().apply { chatId = chat.id chatTitle = chat.title name = TelegramUiHelper.getUserName(user) - latLon = LatLon(content.location.latitude, content.location.longitude) + latLon = when (content) { + is TdApi.MessageLocation -> LatLon(content.location.latitude, content.location.longitude) + is MessageUserLocation -> LatLon(content.lat, content.lon) + else -> null + } if (helper.isGroup(chat)) { photoPath = helper.getUserPhotoPath(user) groupPhotoPath = chat.photo?.small?.local?.path @@ -207,7 +222,35 @@ object TelegramUiHelper { userId = message.senderUserId privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat) chatWithBot = helper.isBot(userId) - lastUpdated = helper.getLastUpdatedTime(message) + lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message) + } + } + + fun userLocationsToChatItem(helper: TelegramHelper, userLocation: LocationMessages.UserLocations): LocationMessagesChatItem? { + val user = helper.getUser(userLocation.userId) + val chat = helper.getChat(userLocation.chatId) + return LocationMessagesChatItem().apply { + if (chat != null) { + chatId = chat.id + chatTitle = chat.title + if (helper.isGroup(chat)) { + photoPath = helper.getUserPhotoPath(user) + groupPhotoPath = chat.photo?.small?.local?.path + } else { + photoPath = user?.profilePhoto?.small?.local?.path + privateChat = helper.isPrivateChat(chat) || helper.isSecretChat(chat) + } + } else { + photoPath = user?.profilePhoto?.small?.local?.path + } + if (user != null) { + name = TelegramUiHelper.getUserName(user) + userId = user.id + } + userLocations = userLocation + grayscalePhotoPath = helper.getUserGreyPhotoPath(user) + placeholderId = R.drawable.img_user_picture + chatWithBot = helper.isBot(userId) } } @@ -259,6 +302,24 @@ object TelegramUiHelper { override fun getVisibleName() = chatTitle } + class LocationMessagesChatItem : ListItem() { + + var userLocations: LocationMessages.UserLocations? = null + internal set + var groupPhotoPath: String? = null + internal set + var privateChat: Boolean = false + internal set + var chatWithBot: Boolean = false + internal set + + override fun canBeOpenedOnMap() = latLon != null + + override fun getMapPointId() = "${chatId}_$userId" + + override fun getVisibleName() = chatTitle + } + class LocationItem : ListItem() { override fun canBeOpenedOnMap() = latLon != null diff --git a/OsmAnd-telegram/src/net/osmand/telegram/notifications/LocationNotification.kt b/OsmAnd-telegram/src/net/osmand/telegram/notifications/LocationNotification.kt index 5e55476462..b41306ebca 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/notifications/LocationNotification.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/notifications/LocationNotification.kt @@ -77,6 +77,6 @@ class LocationNotification(app: TelegramApplication) : TelegramNotification(app, .setStyle(NotificationCompat.BigTextStyle().bigText(notificationText)) } - private fun isShowingChatsNotificationEnabled() = !app.showLocationHelper.isUseOsmandCallback() - && app.isOsmAndInstalled() && app.settings.hasAnyChatToShowOnMap() + private fun isShowingChatsNotificationEnabled() = (!app.showLocationHelper.isUseOsmandCallback() || app.settings.monitoringEnabled) + && app.isAnyOsmAndInstalled() && app.settings.hasAnyChatToShowOnMap() } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/AddNewDeviceBottomSheet.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/AddNewDeviceBottomSheet.kt new file mode 100644 index 0000000000..b562ba3024 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/AddNewDeviceBottomSheet.kt @@ -0,0 +1,186 @@ +package net.osmand.telegram.ui + +import android.app.Dialog +import android.content.Intent +import android.os.Bundle +import android.support.design.widget.BottomSheetBehavior +import android.support.v4.app.Fragment +import android.support.v4.app.FragmentManager +import android.support.v4.content.ContextCompat +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.EditText +import android.widget.ProgressBar +import android.widget.TextView +import net.osmand.telegram.R +import net.osmand.telegram.TelegramSettings +import net.osmand.telegram.ui.views.BottomSheetDialog +import net.osmand.telegram.utils.AndroidNetworkUtils +import net.osmand.telegram.utils.OsmandApiUtils +import org.drinkless.td.libcore.telegram.TdApi +import org.json.JSONException +import org.json.JSONObject + +class AddNewDeviceBottomSheet : BaseDialogFragment() { + + private lateinit var editText: EditText + private lateinit var errorTextDescription: TextView + private lateinit var primaryBtn: TextView + private lateinit var progressBar: ProgressBar + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = BottomSheetDialog(context!!) + dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) + return dialog + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + val mainView = inflater.inflate(R.layout.bottom_sheet_add_new_device, container, false) + + mainView.findViewById(R.id.scroll_view_container).setOnClickListener { dismiss() } + + BottomSheetBehavior.from(mainView.findViewById(R.id.scroll_view)) + .setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { + override fun onStateChanged(bottomSheet: View, newState: Int) { + if (newState == BottomSheetBehavior.STATE_HIDDEN) { + dismiss() + } + } + + override fun onSlide(bottomSheet: View, slideOffset: Float) {} + }) + + editText = mainView.findViewById(R.id.edit_text).apply { + addTextChangedListener(object : + TextWatcher { + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} + + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun afterTextChanged(s: Editable) { + updateErrorTextDescription(s.toString()) + } + }) + } + + errorTextDescription = mainView.findViewById(R.id.error_text_descr) + + progressBar = mainView.findViewById(R.id.progressBar).apply { + indeterminateDrawable.setColorFilter(ContextCompat.getColor(app, R.color.primary_btn_text_light), android.graphics.PorterDuff.Mode.MULTIPLY) + } + + mainView.findViewById(R.id.secondary_btn).apply { + setText(R.string.shared_string_cancel) + setOnClickListener { dismiss() } + } + + primaryBtn = mainView.findViewById(R.id.primary_btn).apply { + setText(R.string.shared_string_add) + setOnClickListener { + val deviceName = editText.text.toString() + updateErrorTextDescription(deviceName) + if (deviceName.isNotEmpty() && deviceName.length < MAX_DEVICE_NAME_LENGTH + && !app.settings.containsShareDeviceWithName(deviceName)) { + val user = app.telegramHelper.getCurrentUser() + if (user != null) { + updatePrimaryBtnAndProgress(true) + createNewDeviceWithName(user, deviceName) + } + } + } + } + return mainView + } + + private fun createNewDeviceWithName(user: TdApi.User, deviceName: String) { + OsmandApiUtils.createNewDevice(app, user, app.telegramHelper.isBot(user.id), deviceName, 0, + object : AndroidNetworkUtils.OnRequestResultListener { + override fun onResult(result: String?) { + updatePrimaryBtnAndProgress(false) + val deviceJson = getDeviceJson(result) + if (deviceJson != null) { + targetFragment?.also { target -> + val intent = Intent().putExtra(DEVICE_JSON, deviceJson.toString()) + target.onActivityResult(targetRequestCode, NEW_DEVICE_REQUEST_CODE, intent) + } + dismiss() + } else { + updateErrorTextDescription(null) + } + } + }) + } + + private fun updateErrorTextDescription(text: String?) { + when { + text == null -> { + errorTextDescription.visibility = View.VISIBLE + errorTextDescription.text = getString(R.string.error_adding_new_device) + } + text.isEmpty() -> { + errorTextDescription.visibility = View.VISIBLE + errorTextDescription.text = getString(R.string.device_name_cannot_be_empty) + } + text.length > MAX_DEVICE_NAME_LENGTH -> { + errorTextDescription.visibility = View.VISIBLE + errorTextDescription.text = getString(R.string.device_name_is_too_long) + } + app.settings.containsShareDeviceWithName(text.toString()) -> { + errorTextDescription.visibility = View.VISIBLE + errorTextDescription.text = getString(R.string.enter_another_device_name) + } + else -> errorTextDescription.visibility = View.INVISIBLE + } + } + + private fun updatePrimaryBtnAndProgress(showProgress: Boolean) { + if (showProgress) { + progressBar.visibility = View.VISIBLE + primaryBtn.text = "" + } else { + progressBar.visibility = View.GONE + primaryBtn.setText(R.string.shared_string_add) + } + } + + private fun getDeviceJson(json: String?): JSONObject? { + if (json != null) { + try { + val jsonResult = JSONObject(json) + val status = jsonResult.getString("status") + if (status == "OK") { + return jsonResult.getJSONObject("device") + } + } catch (e: JSONException) { + } + } + return null + } + + companion object { + const val NEW_DEVICE_REQUEST_CODE = 5 + const val DEVICE_JSON = "device_json" + const val MAX_DEVICE_NAME_LENGTH = 200 + + private const val TAG = "AddNewDeviceBottomSheet" + fun showInstance(fm: FragmentManager, target: Fragment): Boolean { + return try { + AddNewDeviceBottomSheet().apply { + setTargetFragment(target, NEW_DEVICE_REQUEST_CODE) + show(fm, TAG) + } + true + } catch (e: RuntimeException) { + false + } + } + } +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt index 2157e9d17a..c0a52921a9 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/LiveNowTabFragment.kt @@ -1,8 +1,10 @@ package net.osmand.telegram.ui import android.content.Intent +import android.graphics.Color import android.os.Bundle import android.support.v4.app.Fragment +import android.support.v4.widget.SwipeRefreshLayout import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.ListPopupWindow import android.support.v7.widget.RecyclerView @@ -33,6 +35,7 @@ import net.osmand.telegram.utils.OsmandFormatter import net.osmand.telegram.utils.UiUtils.UpdateLocationViewCache import net.osmand.util.MapUtils import org.drinkless.td.libcore.telegram.TdApi +import java.util.* private const val CHAT_VIEW_TYPE = 0 private const val LOCATION_ITEM_VIEW_TYPE = 1 @@ -44,7 +47,6 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage get() = activity?.application as TelegramApplication private val telegramHelper get() = app.telegramHelper - private val osmandAidlHelper get() = app.osmandAidlHelper private val settings get() = app.settings private lateinit var adapter: LiveNowListAdapter @@ -52,11 +54,14 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage private lateinit var openOsmAndBtn: TextView private lateinit var sortByBtn: TextView + private lateinit var lastTelegramUpdateTime: TextView private var location: Location? = null private var heading: Float? = null private var locationUiUpdateAllowed: Boolean = true + private var lastTelegramUpdateStr = "" + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -64,6 +69,10 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage ): View? { val mainView = inflater.inflate(R.layout.fragment_live_now_tab, container, false) val appBarLayout = mainView.findViewById(R.id.app_bar_layout) + + lastTelegramUpdateTime = mainView.findViewById(R.id.last_telegram_update_time) + lastTelegramUpdateStr = getString(R.string.last_update_from_telegram) + ": " + AndroidUtils.addStatusBarPadding19v(context!!, appBarLayout) adapter = LiveNowListAdapter() mainView.findViewById(R.id.recycler_view).apply { @@ -92,11 +101,19 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage } } + mainView.findViewById(R.id.swipe_refresh).apply { + setOnRefreshListener { + app.shareLocationHelper.checkNetworkType() + updateList() + isRefreshing = false + } + setColorSchemeColors(Color.RED, Color.GREEN, Color.BLUE, Color.CYAN) + } + openOsmAndBtn = mainView.findViewById(R.id.open_osmand_btn).apply { setOnClickListener { - val pack = settings.appToConnectPackage - if (AndroidUtils.isAppInstalled(context, pack)) { - activity?.packageManager?.getLaunchIntentForPackage(pack)?.also { intent -> + if (app.isOsmAndInstalled()) { + activity?.packageManager?.getLaunchIntentForPackage(settings.appToConnectPackage)?.also { intent -> startActivity(intent) } } else { @@ -164,6 +181,10 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage updateList() } + override fun onTelegramChatCreated(chat: TdApi.Chat) { + updateList() + } + override fun onTelegramUserChanged(user: TdApi.User) { updateList() } @@ -278,6 +299,12 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage } } + if (res.isEmpty()) { + lastTelegramUpdateTime.visibility = View.VISIBLE + lastTelegramUpdateTime.text = OsmandFormatter.getListItemLiveTimeDescr(app, telegramHelper.lastTelegramUpdateTime, lastTelegramUpdateStr) + } else { + lastTelegramUpdateTime.visibility = View.GONE + } adapter.items = sortAdapterItems(res) } @@ -423,9 +450,11 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage openOnMapView?.isEnabled = canBeOpenedOnMap if (canBeOpenedOnMap) { openOnMapView?.setOnClickListener { - if (!app.isOsmAndInstalled()) { + if (!app.isAnyOsmAndInstalled()) { showOsmAndMissingDialog() - } else { + } else if (!app.isOsmAndChosen() || (app.isOsmAndChosen() && !app.isOsmAndInstalled())) { + fragmentManager?.also { ChooseOsmAndBottomSheet.showInstance(it, this@LiveNowTabFragment) } + } else if(app.isOsmAndInstalled()){ app.showLocationHelper.showLocationOnMap(item, staleLocation) } } @@ -446,6 +475,13 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage } holder.bottomShadow?.visibility = if (lastItem) View.VISIBLE else View.GONE + if (lastItem) { + holder.lastTelegramUpdateTime?.visibility = View.VISIBLE + holder.lastTelegramUpdateTime?.text = OsmandFormatter.getListItemLiveTimeDescr(app, telegramHelper.lastTelegramUpdateTime, lastTelegramUpdateStr) + } else { + holder.lastTelegramUpdateTime?.visibility = View.GONE + } + if (item is ChatItem && holder is ChatViewHolder) { val nextIsLocation = !lastItem && (items[position + 1] is LocationItem || !sortByGroup) val chatId = item.chatId @@ -511,17 +547,21 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage settings.showChatOnMap(chatId, allSelected) if (settings.hasAnyChatToShowOnMap()) { - if (!app.isOsmAndInstalled()) { + if (!app.isAnyOsmAndInstalled()) { if (allSelected) { showOsmAndMissingDialog() } + } else if (!app.isOsmAndChosen() || (app.isOsmAndChosen() && !app.isOsmAndInstalled())) { + fragmentManager?.also { ChooseOsmAndBottomSheet.showInstance(it, this@LiveNowTabFragment) } } else { - if (allSelected) { - app.showLocationHelper.showChatMessages(chatId) - } else { - app.showLocationHelper.hideChatMessages(chatId) + if (app.isOsmAndInstalled()) { + if (allSelected) { + app.showLocationHelper.showChatMessages(chatId) + } else { + app.showLocationHelper.hideChatMessages(chatId) + } + app.showLocationHelper.startShowingLocation() } - app.showLocationHelper.startShowingLocation() } } else { app.showLocationHelper.stopShowingLocation() @@ -545,6 +585,7 @@ class LiveNowTabFragment : Fragment(), TelegramListener, TelegramIncomingMessage val distanceText: TextView? = view.findViewById(R.id.distance_text) val description: TextView? = view.findViewById(R.id.description) val bottomShadow: View? = view.findViewById(R.id.bottom_shadow) + val lastTelegramUpdateTime: TextView? = view.findViewById(R.id.last_telegram_update_time) abstract fun getOpenOnMapClickView(): View? } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/MainActivity.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/MainActivity.kt index 9d372ca24d..b36a164c01 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/MainActivity.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/MainActivity.kt @@ -27,6 +27,7 @@ import net.osmand.telegram.ui.MyLocationTabFragment.ActionButtonsListener import net.osmand.telegram.ui.views.LockableViewPager import net.osmand.telegram.utils.* import org.drinkless.td.libcore.telegram.TdApi +import java.io.File import java.lang.ref.WeakReference const val OPEN_MY_LOCATION_TAB_KEY = "open_my_location_tab" @@ -35,6 +36,7 @@ private const val PERMISSION_REQUEST_LOCATION = 1 private const val MY_LOCATION_TAB_POS = 0 private const val LIVE_NOW_TAB_POS = 1 +private const val TIMELINE_TAB_POS = 2 class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListener, TelegramIncomingMessagesListener { @@ -54,6 +56,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene private var myLocationTabFragment: MyLocationTabFragment? = null private var liveNowTabFragment: LiveNowTabFragment? = null + private var timelineTabFragment: TimelineTabFragment? = null private lateinit var buttonsBar: LinearLayout private lateinit var bottomNav: BottomNavigationView @@ -72,7 +75,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene val viewPager = findViewById(R.id.view_pager).apply { swipeLocked = true - offscreenPageLimit = 2 + offscreenPageLimit = 3 adapter = ViewPagerAdapter(supportFragmentManager) } @@ -82,11 +85,13 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene when (it.itemId) { R.id.action_my_location -> pos = MY_LOCATION_TAB_POS R.id.action_live_now -> pos = LIVE_NOW_TAB_POS + R.id.action_timeline -> pos = TIMELINE_TAB_POS } if (pos != -1 && pos != viewPager.currentItem) { when (pos) { MY_LOCATION_TAB_POS -> liveNowTabFragment?.tabClosed() LIVE_NOW_TAB_POS -> liveNowTabFragment?.tabOpened() + TIMELINE_TAB_POS -> liveNowTabFragment?.tabClosed() } viewPager.currentItem = pos return@setOnNavigationItemSelectedListener true @@ -139,10 +144,10 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene if (fragment is TelegramListener) { listeners.add(WeakReference(fragment)) } - if (fragment is MyLocationTabFragment) { - myLocationTabFragment = fragment - } else if (fragment is LiveNowTabFragment) { - liveNowTabFragment = fragment + when (fragment) { + is MyLocationTabFragment -> myLocationTabFragment = fragment + is LiveNowTabFragment -> liveNowTabFragment = fragment + is TimelineTabFragment -> timelineTabFragment = fragment } } @@ -160,7 +165,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene if (AndroidUtils.isLocationPermissionAvailable(this)) { app.locationProvider.resumeAllUpdates() } - if (settings.hasAnyChatToShowOnMap() && !app.isOsmAndInstalled()) { + if (settings.hasAnyChatToShowOnMap() && !app.isAnyOsmAndInstalled()) { showOsmandMissingDialog() } } @@ -178,7 +183,6 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene override fun onStop() { super.onStop() settings.save() - app.messagesDbHelper.saveMessages() } override fun onDestroy() { @@ -218,7 +222,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene if (user != null) { OsmandApiUtils.updateSharingDevices(app, user.id) if (settings.currentSharingMode.isEmpty()) { - settings.currentSharingMode = user.id.toString() + settings.updateCurrentSharingMode(user.id.toString()) } } } @@ -254,6 +258,12 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene } } + override fun onTelegramChatCreated(chat: TdApi.Chat) { + runOnUi { + listeners.forEach { it.get()?.onTelegramChatCreated(chat) } + } + } + override fun onTelegramUserChanged(user: TdApi.User) { val photoPath = telegramHelper.getUserPhotoPath(user) if (photoPath != null) { @@ -311,6 +321,15 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene android.os.Process.killProcess(android.os.Process.myPid()) } + fun shareGpx(path: String) { + val fileUri = AndroidUtils.getUriForFile(app, File(path)) + val sendIntent = Intent(Intent.ACTION_SEND) + sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri) + sendIntent.type = "application/gpx+xml" + sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + startActivity(sendIntent) + } + fun loginTelegram() { if (telegramHelper.getTelegramAuthorizationState() != TelegramAuthorizationState.CLOSED) { telegramHelper.logout() @@ -321,7 +340,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene fun logoutTelegram(silent: Boolean = false) { if (telegramHelper.getTelegramAuthorizationState() == TelegramHelper.TelegramAuthorizationState.READY) { if (app.isInternetConnectionAvailable) { - app.messagesDbHelper.clearMessages() + app.locationMessages.clearBufferedMessages() settings.clear() telegramHelper.logout() } else { @@ -429,7 +448,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene settings.stopSharingLocationToChats() app.shareLocationHelper.stopSharingLocation() } - if (settings.hasAnyChatToShowOnMap() && !app.isOsmAndInstalled()) { + if (settings.hasAnyChatToShowOnMap() && !app.isAnyOsmAndInstalled()) { showOsmandMissingDialog() } } @@ -457,7 +476,7 @@ class MainActivity : AppCompatActivity(), TelegramListener, ActionButtonsListene class ViewPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) { - private val fragments = listOf(MyLocationTabFragment(), LiveNowTabFragment()) + private val fragments = listOf(MyLocationTabFragment(), LiveNowTabFragment(), TimelineTabFragment()) override fun getItem(position: Int) = fragments[position] diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt index 41fa47db5b..312752229f 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/MyLocationTabFragment.kt @@ -28,6 +28,7 @@ import net.osmand.telegram.utils.OsmandFormatter import org.drinkless.td.libcore.telegram.TdApi private const val SELECTED_CHATS_KEY = "selected_chats" +private const val SELECTED_CHATS_USERS = "selected_users" private const val SHARE_LOCATION_CHAT = 1 private const val DEFAULT_CHAT = 0 @@ -71,11 +72,12 @@ class MyLocationTabFragment : Fragment(), TelegramListener { private lateinit var appBarOutlineProvider: ViewOutlineProvider private val selectedChats = HashSet() + private val selectedUsers = HashSet() private var actionButtonsListener: ActionButtonsListener? = null private var sharingMode = false - + private var updateEnable: Boolean = false override fun onCreateView( @@ -94,12 +96,17 @@ class MyLocationTabFragment : Fragment(), TelegramListener { searchBoxSidesMargin = resources.getDimensionPixelSize(R.dimen.content_padding_half) sharingMode = settings.hasAnyChatToShareLocation() - + savedInstanceState?.apply { - selectedChats.addAll(getLongArray(SELECTED_CHATS_KEY).toSet()) - if (selectedChats.isNotEmpty()) { - actionButtonsListener?.switchButtonsVisibility(true) + val chatsArray = getLongArray(SELECTED_CHATS_KEY) + val usersArray = getLongArray(SELECTED_CHATS_KEY) + if (chatsArray != null) { + selectedChats.addAll(chatsArray.toSet()) } + if (usersArray != null) { + selectedUsers.addAll(usersArray.toSet()) + } + actionButtonsListener?.switchButtonsVisibility((selectedUsers.isNotEmpty() || selectedChats.isNotEmpty())) } val mainView = inflater.inflate(R.layout.fragment_my_location_tab, container, false) @@ -130,7 +137,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener { setupOptionsBtn(optionsBtn) setupOptionsBtn(mainView.findViewById(R.id.options_title)) } - + imageContainer = mainView.findViewById(R.id.image_container) titleContainer = mainView.findViewById(R.id.title_container).apply { AndroidUtils.addStatusBarPadding19v(context, this) @@ -139,8 +146,10 @@ class MyLocationTabFragment : Fragment(), TelegramListener { mainView.findViewById(R.id.status_title).apply { val sharingStatus = getString(R.string.sharing_enabled) val spannable = SpannableString(sharingStatus) - spannable.setSpan(ForegroundColorSpan(app.uiUtils.getActiveColor()), - sharingStatus.indexOf(" "), sharingStatus.length, 0) + spannable.setSpan( + ForegroundColorSpan(app.uiUtils.getActiveColor()), + sharingStatus.indexOf(" "), sharingStatus.length, 0 + ) text = spannable } @@ -191,7 +200,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener { mainView.findViewById(R.id.stop_all_sharing_row).setOnClickListener { fragmentManager?.also { fm -> - DisableSharingBottomSheet.showInstance(fm, this, adapter.chats.size) + DisableSharingBottomSheet.showInstance(fm, this, adapter.items.size) } } @@ -215,14 +224,13 @@ class MyLocationTabFragment : Fragment(), TelegramListener { updateContent() } } - + return mainView } override fun onResume() { super.onResume() updateCurrentUserPhoto() - telegramHelper.getActiveLiveLocationMessages(null) updateContent() updateEnable = true startHandler() @@ -232,10 +240,11 @@ class MyLocationTabFragment : Fragment(), TelegramListener { super.onPause() updateEnable = false } - + override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putLongArray(SELECTED_CHATS_KEY, selectedChats.toLongArray()) + outState.putLongArray(SELECTED_CHATS_USERS, selectedUsers.toLongArray()) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -274,7 +283,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener { TelegramHelper.TelegramAuthorizationState.LOGGING_OUT, TelegramHelper.TelegramAuthorizationState.CLOSED, TelegramHelper.TelegramAuthorizationState.UNKNOWN -> { - adapter.chats = mutableListOf() + adapter.items = mutableListOf() } else -> Unit } @@ -292,6 +301,11 @@ class MyLocationTabFragment : Fragment(), TelegramListener { updateContent() } + override fun onTelegramChatCreated(chat: TdApi.Chat) { + sharingMode = settings.hasAnyChatToShareLocation() + updateContent() + } + override fun onTelegramUserChanged(user: TdApi.User) { if (user.id == telegramHelper.getCurrentUser()?.id) { updateCurrentUserPhoto() @@ -303,9 +317,9 @@ class MyLocationTabFragment : Fragment(), TelegramListener { } fun onPrimaryBtnClick() { - if (selectedChats.isNotEmpty()) { + if (selectedChats.isNotEmpty() || selectedUsers.isNotEmpty()) { val fm = fragmentManager ?: return - SetTimeDialogFragment.showInstance(fm, selectedChats, this) + SetTimeDialogFragment.showInstance(fm, selectedChats, selectedUsers, this) } } @@ -336,7 +350,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener { } }, ADAPTER_UPDATE_INTERVAL_MIL) } - + private fun animateStartSharingBtn(show: Boolean) { if (startSharingBtn.visibility == View.VISIBLE) { val scale = if (show) 1f else 0f @@ -348,9 +362,10 @@ class MyLocationTabFragment : Fragment(), TelegramListener { .start() } } - + private fun clearSelection() { selectedChats.clear() + selectedUsers.clear() adapter.notifyDataSetChanged() actionButtonsListener?.switchButtonsVisibility(false) } @@ -436,25 +451,31 @@ class MyLocationTabFragment : Fragment(), TelegramListener { textContainer.visibility = if (sharingMode) View.GONE else View.VISIBLE titleContainer.visibility = if (sharingMode) View.VISIBLE else View.GONE startSharingBtn.visibility = if (sharingMode) View.VISIBLE else View.GONE - headerParams.scrollFlags = if (sharingMode) 0 else AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL + headerParams.scrollFlags = + if (sharingMode) 0 else AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL stopSharingSwitcher.isChecked = true appBarScrollRange = -1 } private fun updateSharingStatus() { if (sharingMode) { - if (settings.sharingStatusChanges.isEmpty()) { - settings.updateSharingStatusHistory() - } + settings.updateSharingStatusHistory() val sharingStatus = settings.sharingStatusChanges.last() sharingStatusTitle.text = sharingStatus.getTitle(app) - sharingStatusIcon.setImageDrawable(app.uiUtils.getIcon(sharingStatus.statusType.iconId, sharingStatus.statusType.iconColorRes)) + sharingStatusIcon.setImageDrawable( + app.uiUtils.getIcon( + sharingStatus.statusType.iconId, + sharingStatus.statusType.iconColorRes + ) + ) } } private fun updateList() { + val items: MutableList = mutableListOf() val chats: MutableList = mutableListOf() val currentUser = telegramHelper.getCurrentUser() + val contacts = telegramHelper.getContacts() val chatList = if (sharingMode) { settings.getShareLocationChats() } else { @@ -473,27 +494,57 @@ class MyLocationTabFragment : Fragment(), TelegramListener { chats.add(chat) } } + items.addAll(chats) + if (!sharingMode) { + for (user in contacts.values) { + val containsInChats = + chats.any { telegramHelper.getUserIdFromChatType(it.type) == user.id } + if ((!sharingMode && settings.isSharingLocationToUser(user.id)) || user.id == currentUser?.id || containsInChats) { + continue + } + items.add(user) + } + } if (sharingMode && settings.hasAnyChatToShareLocation()) { - adapter.chats = sortAdapterItems(chats) + adapter.items = sortAdapterItems(items) } else { - adapter.chats = chats + adapter.items = items } } - private fun sortAdapterItems(list: MutableList): MutableList { - list.sortWith(Comparator { o1, o2 -> o1.title.compareTo(o2.title) }) + private fun sortAdapterItems(list: MutableList): MutableList { + list.sortWith(Comparator { o1, o2 -> + val title1 = when (o1) { + is TdApi.Chat -> o1.title + is TdApi.User -> TelegramUiHelper.getUserName(o1) + else -> "" + } + val title2 = when (o2) { + is TdApi.Chat -> o2.title + is TdApi.User -> TelegramUiHelper.getUserName(o2) + else -> "" + } + title1.compareTo(title2) + }) return list } - - inner class MyLocationListAdapter : RecyclerView.Adapter() { - var chats = mutableListOf() + + inner class MyLocationListAdapter : + RecyclerView.Adapter() { + var items = mutableListOf() set(value) { field = value notifyDataSetChanged() } override fun getItemViewType(position: Int): Int { - return if (settings.isSharingLocationToChat(chats[position].id) && sharingMode) { + val item = items[position] + val id = when (item) { + is TdApi.Chat -> item.id + is TdApi.User -> item.id.toLong() + else -> -1 + } + return if (settings.isSharingLocationToChat(id) && sharingMode) { SHARE_LOCATION_CHAT } else { DEFAULT_CHAT @@ -518,15 +569,35 @@ class MyLocationTabFragment : Fragment(), TelegramListener { @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { - val chat = chats[position] + val item = items[position] + val isChat = item is TdApi.Chat + val itemId = if (isChat) { + (item as TdApi.Chat).id + } else { + (item as TdApi.User).id.toLong() + } + val lastItem = position == itemCount - 1 - val placeholderId = if (telegramHelper.isGroup(chat)) R.drawable.img_group_picture else R.drawable.img_user_picture - val live = settings.isSharingLocationToChat(chat.id) - val shareInfo = settings.getChatsShareInfo()[chat.id] + val placeholderId = + if (isChat && telegramHelper.isGroup(item as TdApi.Chat)) R.drawable.img_group_picture else R.drawable.img_user_picture + val live = (isChat && settings.isSharingLocationToChat(itemId)) + val shareInfo = if (isChat) settings.getChatsShareInfo()[itemId] else null - TelegramUiHelper.setupPhoto(app, holder.icon, chat.photo?.small?.local?.path, placeholderId, false) - holder.title?.text = chat.title + val photoPath = when (item) { + is TdApi.Chat -> item.photo?.small?.local?.path + is TdApi.User -> item.profilePhoto?.small?.local?.path + else -> null + } + TelegramUiHelper.setupPhoto(app, holder.icon, photoPath, placeholderId, false) + + val title = when (item) { + is TdApi.Chat -> item.title + is TdApi.User -> TelegramUiHelper.getUserName(item) + else -> null + } + + holder.title?.text = title if (holder is ChatViewHolder) { holder.description?.visibility = View.GONE if (live) { @@ -535,21 +606,33 @@ class MyLocationTabFragment : Fragment(), TelegramListener { holder.checkBox?.apply { visibility = View.VISIBLE setOnCheckedChangeListener(null) - isChecked = selectedChats.contains(chat.id) + isChecked = if (isChat) { + selectedChats.contains(itemId) + } else { + selectedUsers.contains(itemId) + } setOnCheckedChangeListener { _, isChecked -> if (isChecked) { - selectedChats.add(chat.id) + if (isChat) { + selectedChats.add(itemId) + } else { + selectedUsers.add(itemId) + } } else { - selectedChats.remove(chat.id) + if (isChat) { + selectedChats.remove(itemId) + } else { + selectedUsers.remove(itemId) + } } - actionButtonsListener?.switchButtonsVisibility(selectedChats.isNotEmpty()) + actionButtonsListener?.switchButtonsVisibility(selectedChats.isNotEmpty() || selectedUsers.isNotEmpty()) } } } holder.bottomShadow?.visibility = if (lastItem) View.VISIBLE else View.GONE holder.itemView.setOnClickListener { if (live) { - settings.shareLocationToChat(chat.id, false) + settings.shareLocationToChat(itemId, false) shareLocationHelper.stopSharingLocation() notifyItemChanged(position) } else { @@ -563,18 +646,19 @@ class MyLocationTabFragment : Fragment(), TelegramListener { isChecked = live setOnCheckedChangeListener { _, isChecked -> if (!isChecked) { - settings.shareLocationToChat(chat.id, false) + settings.shareLocationToChat(itemId, false) if (shareInfo != null) { telegramHelper.stopSendingLiveLocationToChat(shareInfo) } - removeItem(chat) + removeItem(item) } } } val duration = shareInfo?.userSetLivePeriod if (duration != null && duration > 0) { - holder.descriptionDuration?.text = OsmandFormatter.getFormattedDuration(context!!, duration) + holder.descriptionDuration?.text = + OsmandFormatter.getFormattedDuration(context!!, duration) holder.description?.apply { visibility = View.VISIBLE text = "${getText(R.string.sharing_time)}:" @@ -582,16 +666,32 @@ class MyLocationTabFragment : Fragment(), TelegramListener { } val expiresIn = shareInfo?.getChatLiveMessageExpireTime() ?: 0 - + holder.textInArea?.apply { - val time = shareInfo?.additionalActiveTime ?: ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0] + val time = + shareInfo?.additionalActiveTime ?: ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0] visibility = View.VISIBLE - text = "${getText(R.string.plus)} ${OsmandFormatter.getFormattedDuration(context!!, time)}" + text = "+ ${OsmandFormatter.getFormattedDuration(context!!, time)}" setOnClickListener { val expireTime = shareInfo?.getChatLiveMessageExpireTime() ?: 0 - val newLivePeriod = expireTime + (shareInfo?.additionalActiveTime ?: ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]) - val nextAdditionalActiveTime = shareInfo?.getNextAdditionalActiveTime() ?: ADDITIONAL_ACTIVE_TIME_VALUES_SEC[1] - settings.shareLocationToChat(chat.id, true, newLivePeriod, nextAdditionalActiveTime) + val newLivePeriod = expireTime + (shareInfo?.additionalActiveTime + ?: ADDITIONAL_ACTIVE_TIME_VALUES_SEC[0]) + val nextAdditionalActiveTime = shareInfo?.getNextAdditionalActiveTime() + ?: ADDITIONAL_ACTIVE_TIME_VALUES_SEC[1] + if (isChat) { + settings.shareLocationToChat( + itemId, + true, + newLivePeriod, + nextAdditionalActiveTime + ) + } else { + settings.shareLocationToUser( + itemId.toInt(), + newLivePeriod, + nextAdditionalActiveTime + ) + } notifyItemChanged(position) } } @@ -608,17 +708,30 @@ class MyLocationTabFragment : Fragment(), TelegramListener { holder.stopSharingSecondPart?.apply { visibility = getStopSharingVisibility(expiresIn) - text = "(${getString(R.string.in_time, - OsmandFormatter.getFormattedDuration(context!!, expiresIn, true))})" + text = "(${getString( + R.string.in_time, + OsmandFormatter.getFormattedDuration(context!!, expiresIn, true) + )})" + } + holder.gpsPointsCollected?.apply { + if (shareInfo != null) { + text = " ${shareInfo.sentMessages}" + } + } + holder.gpsPointsSent?.apply { + if (shareInfo != null) { + text = getString(R.string.gps_points_in_buffer, shareInfo.pendingTdLib + app.locationMessages.getBufferedMessagesCountForChat(shareInfo.chatId)) + } } } } - private fun getStopSharingVisibility(expiresIn: Long) = if (expiresIn > 0) View.VISIBLE else View.INVISIBLE + private fun getStopSharingVisibility(expiresIn: Long) = + if (expiresIn > 0) View.VISIBLE else View.INVISIBLE - private fun removeItem(chat: TdApi.Chat) { - chats.remove(chat) - if (chats.isEmpty()) { + private fun removeItem(chat: TdApi.Object) { + items.remove(chat) + if (items.isEmpty()) { sharingMode = false updateContent() shareLocationHelper.stopSharingLocation() @@ -627,7 +740,7 @@ class MyLocationTabFragment : Fragment(), TelegramListener { } } - override fun getItemCount() = chats.size + override fun getItemCount() = items.size abstract inner class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) { val icon: ImageView? = view.findViewById(R.id.icon) @@ -647,10 +760,12 @@ class MyLocationTabFragment : Fragment(), TelegramListener { val stopSharingDescr: TextView? = view.findViewById(R.id.stop_in) val stopSharingFirstPart: TextView? = view.findViewById(R.id.ending_in_first_part) val stopSharingSecondPart: TextView? = view.findViewById(R.id.ending_in_second_part) + val gpsPointsCollected: TextView? = view.findViewById(R.id.gps_points_collected) + val gpsPointsSent: TextView? = view.findViewById(R.id.gps_points_in_buffer_txt) } } interface ActionButtonsListener { fun switchButtonsVisibility(visible: Boolean) } -} +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt index c7d49771fe..c3007c9217 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/SetTimeDialogFragment.kt @@ -14,13 +14,14 @@ import android.widget.TextView import net.osmand.Location import net.osmand.data.LatLon import net.osmand.telegram.R -import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener import net.osmand.telegram.TelegramLocationProvider.TelegramCompassListener +import net.osmand.telegram.TelegramLocationProvider.TelegramLocationListener import net.osmand.telegram.helpers.ShareLocationHelper import net.osmand.telegram.helpers.TelegramUiHelper import net.osmand.telegram.ui.SetTimeDialogFragment.SetTimeListAdapter.ChatViewHolder import net.osmand.telegram.utils.AndroidUtils import net.osmand.telegram.utils.OsmandFormatter +import net.osmand.telegram.utils.OsmandLocationUtils import net.osmand.telegram.utils.UiUtils import net.osmand.util.MapUtils import org.drinkless.td.libcore.telegram.TdApi @@ -36,6 +37,7 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te private lateinit var timeForAllValue: TextView private val chatLivePeriods = HashMap() + private val userLivePeriods = HashMap() private var location: Location? = null private var heading: Float? = null @@ -90,6 +92,9 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te chatLivePeriods.forEach { (chatId, livePeriod) -> settings.shareLocationToChat(chatId, true, livePeriod) } + userLivePeriods.forEach { (userId, livePeriod) -> + settings.shareLocationToUser(userId.toInt(), livePeriod) + } app.shareLocationHelper.startSharingLocation() targetFragment?.also { it.onActivityResult(targetRequestCode, LOCATION_SHARED_REQUEST_CODE, null) @@ -121,7 +126,13 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te chats.add(id) chats.add(livePeriod) } + val users = mutableListOf() + for ((id, livePeriod) in userLivePeriods) { + users.add(id) + users.add(livePeriod) + } outState.putLongArray(CHATS_KEY, chats.toLongArray()) + outState.putLongArray(USERS_KEY, users.toLongArray()) } override fun updateLocation(location: Location?) { @@ -167,17 +178,27 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te private fun readFromBundle(bundle: Bundle?) { chatLivePeriods.clear() + userLivePeriods.clear() bundle?.getLongArray(CHATS_KEY)?.also { for (i in 0 until it.size step 2) { val livePeriod = settings.getChatLivePeriod(it[i]) chatLivePeriods[it[i]] = livePeriod ?: it[i + 1] } } + bundle?.getLongArray(USERS_KEY)?.also { + for (j in 0 until it.size step 2) { + val livePeriod = settings.getChatLivePeriod(it[j]) + userLivePeriods[it[j]] = livePeriod ?: it[j + 1] + } + } } private fun getTimeForAll(useDefValue: Boolean = false): Long { val returnVal = if (useDefValue) DEFAULT_VISIBLE_TIME_SECONDS else NO_VALUE - val iterator = chatLivePeriods.values.iterator() + val allTime = mutableListOf() + allTime.addAll(chatLivePeriods.values) + allTime.addAll(userLivePeriods.values) + val iterator = allTime.iterator() if (!iterator.hasNext()) { return returnVal } @@ -202,7 +223,7 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te } } - private fun selectDuration(id: Long? = null) { + private fun selectDuration(id: Long? = null, isChat: Boolean = true) { val timeForAll = getTimeForAll(true) val defSeconds = if (id == null) timeForAll else chatLivePeriods[id] ?: timeForAll val (defHours, defMinutes) = secondsToHoursAndMinutes(defSeconds) @@ -213,11 +234,18 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te TimeUnit.MINUTES.toSeconds(minutes.toLong()) if (seconds >= ShareLocationHelper.MIN_LOCATION_MESSAGE_LIVE_PERIOD_SEC) { if (id != null) { - chatLivePeriods[id] = seconds + if (isChat) { + chatLivePeriods[id] = seconds + } else { + userLivePeriods[id] = seconds + } } else { chatLivePeriods.keys.forEach { chatLivePeriods[it] = seconds } + userLivePeriods.keys.forEach { + userLivePeriods[it] = seconds + } } updateTimeForAllRow() adapter.notifyDataSetChanged() @@ -242,17 +270,19 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te } private fun updateList() { - val chats: MutableList = mutableListOf() + val items: MutableList = mutableListOf() telegramHelper.getChatList().filter { chatLivePeriods.keys.contains(it.chatId) } .forEach { orderedChat -> - telegramHelper.getChat(orderedChat.chatId)?.also { chats.add(it) } + telegramHelper.getChat(orderedChat.chatId)?.also { items.add(it) } } - adapter.chats = chats + telegramHelper.getContacts().values.filter { userLivePeriods.keys.contains(it.id.toLong()) } + .forEach { user -> items.add(user) } + adapter.items = items } inner class SetTimeListAdapter : RecyclerView.Adapter() { - var chats: List = emptyList() + var items: List = emptyList() set(value) { field = value notifyDataSetChanged() @@ -265,21 +295,41 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te } override fun onBindViewHolder(holder: ChatViewHolder, position: Int) { - val chat = chats[position] - val placeholderId = if (telegramHelper.isGroup(chat)) R.drawable.img_group_picture else R.drawable.img_user_picture + val item = items[position] + val isChat = item is TdApi.Chat + val itemId = if (isChat) { + (item as TdApi.Chat).id + } else { + (item as TdApi.User).id.toLong() + } - TelegramUiHelper.setupPhoto(app, holder.icon, chat.photo?.small?.local?.path, placeholderId, false) - holder.title?.text = chat.title + val placeholderId = if (isChat && telegramHelper.isGroup((item as TdApi.Chat))) R.drawable.img_group_picture else R.drawable.img_user_picture - if (telegramHelper.isGroup(chat)) { + val photoPath = when (item) { + is TdApi.Chat -> item.photo?.small?.local?.path + is TdApi.User -> item.profilePhoto?.small?.local?.path + else -> null + } + + TelegramUiHelper.setupPhoto(app, holder.icon, photoPath, placeholderId, false) + + val title = when (item) { + is TdApi.Chat -> item.title + is TdApi.User -> TelegramUiHelper.getUserName(item) + else -> null + } + + holder.title?.text = title + + if (isChat && telegramHelper.isGroup((item as TdApi.Chat))) { holder.locationViewContainer?.visibility = View.GONE holder.description?.visibility = View.VISIBLE holder.description?.text = getString(R.string.shared_string_group) } else { - val message = telegramHelper.getChatMessages(chat.id).firstOrNull() + val message = telegramHelper.getChatMessages(itemId).firstOrNull() val content = message?.content if (message != null && content is TdApi.MessageLocation && (location != null && content.location != null)) { - val lastUpdated = telegramHelper.getLastUpdatedTime(message) + val lastUpdated = OsmandLocationUtils.getLastUpdatedTime(message) holder.description?.visibility = View.VISIBLE holder.description?.text = OsmandFormatter.getListItemLiveTimeDescr(app, lastUpdated) @@ -301,15 +351,19 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te holder.textInArea?.apply { visibility = View.VISIBLE - chatLivePeriods[chat.id]?.also { text = formatLivePeriod(it) } + if (isChat) { + chatLivePeriods[itemId]?.also { text = formatLivePeriod(it) } + } else { + userLivePeriods[itemId]?.also { text = formatLivePeriod(it) } + } } holder.bottomShadow?.visibility = View.GONE holder.itemView.setOnClickListener { - selectDuration(chat.id) + selectDuration(itemId, isChat) } } - override fun getItemCount() = chats.size + override fun getItemCount() = items.size inner class ChatViewHolder(val view: View) : RecyclerView.ViewHolder(view) { val icon: ImageView? = view.findViewById(R.id.icon) @@ -329,18 +383,31 @@ class SetTimeDialogFragment : BaseDialogFragment(), TelegramLocationListener, Te private const val TAG = "SetTimeDialogFragment" private const val CHATS_KEY = "chats_key" + private const val USERS_KEY = "users_key" private const val DEFAULT_VISIBLE_TIME_SECONDS = 60 * 60L // 1 hour private const val NO_VALUE = -1L - fun showInstance(fm: FragmentManager, chatIds: Set, target: Fragment): Boolean { + fun showInstance(fm: FragmentManager, chatIds: Set, usersIds: Set, target: Fragment): Boolean { return try { val chats = mutableListOf() for (id in chatIds) { chats.add(id) chats.add(DEFAULT_VISIBLE_TIME_SECONDS) } + val users = mutableListOf() + for (id in usersIds) { + users.add(id) + users.add(DEFAULT_VISIBLE_TIME_SECONDS) + } SetTimeDialogFragment().apply { - arguments = Bundle().apply { putLongArray(CHATS_KEY, chats.toLongArray()) } + arguments = Bundle().apply { + if (chats.isNotEmpty()) { + putLongArray(CHATS_KEY, chats.toLongArray()) + } + if (users.isNotEmpty()) { + putLongArray(USERS_KEY, users.toLongArray()) + } + } setTargetFragment(target, LOCATION_SHARED_REQUEST_CODE) show(fm, TAG) } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt index 1d749d87fb..44db164777 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/SettingsDialogFragment.kt @@ -1,11 +1,14 @@ package net.osmand.telegram.ui import android.content.Intent +import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle import android.support.v4.app.FragmentManager import android.support.v7.widget.ListPopupWindow import android.support.v7.widget.Toolbar +import android.text.SpannableStringBuilder +import android.text.style.ForegroundColorSpan import android.view.Gravity import android.view.LayoutInflater import android.view.View @@ -14,13 +17,20 @@ import android.widget.* import net.osmand.telegram.R import net.osmand.telegram.TelegramSettings import net.osmand.telegram.TelegramSettings.DurationPref +import net.osmand.telegram.helpers.TelegramHelper.Companion.OSMAND_BOT_USERNAME import net.osmand.telegram.helpers.TelegramUiHelper import net.osmand.telegram.utils.AndroidUtils +import net.osmand.telegram.utils.OsmandApiUtils import org.drinkless.td.libcore.telegram.TdApi +import org.json.JSONObject class SettingsDialogFragment : BaseDialogFragment() { private val uiUtils get() = app.uiUtils + private lateinit var shareAsContainer: ViewGroup + private lateinit var shareAsDescription: TextView + + private var shareAsDescriptionHidden = true override fun onCreateView( inflater: LayoutInflater, @@ -65,6 +75,36 @@ class SettingsDialogFragment : BaseDialogFragment() { } } + shareAsDescription = mainView.findViewById(R.id.share_as_description).apply { + text = getText(R.string.share_location_as_description) + setOnClickListener { + updateShareAsDescription() + } + } + + shareAsContainer = mainView.findViewById(R.id.share_as_container) + val user = telegramHelper.getCurrentUser() + if (user != null) { + addItemToContainer(inflater, shareAsContainer, user.id.toString(), TelegramUiHelper.getUserName(user)) + } + settings.getShareDevices().forEach { + addItemToContainer(inflater, shareAsContainer, it.externalId, it.deviceName) + } + + mainView.findViewById(R.id.add_new_device_title) + .setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light)) + + mainView.findViewById(R.id.add_new_device_icon) + .setImageDrawable(getAddNewDeviceIcon()) + + mainView.findViewById(R.id.add_new_device_btn).apply { + setOnClickListener { + fragmentManager?.also { fm -> + AddNewDeviceBottomSheet.showInstance(fm, this@SettingsDialogFragment) + } + } + } + container = mainView.findViewById(R.id.osmand_connect_container) for (appConn in TelegramSettings.AppConnect.values()) { val pack = appConn.appPackage @@ -106,15 +146,6 @@ class SettingsDialogFragment : BaseDialogFragment() { } updateSelectedAppConn() - container = mainView.findViewById(R.id.share_as_container) - val user = telegramHelper.getCurrentUser() - if (user != null) { - addItemToContainer(inflater, container, user.id.toString(), TelegramUiHelper.getUserName(user)) - } - settings.shareDevicesIds.forEach { - addItemToContainer(inflater, container, it.key, it.value) - } - if (user != null) { TelegramUiHelper.setupPhoto( app, @@ -151,6 +182,21 @@ class SettingsDialogFragment : BaseDialogFragment() { logoutTelegram() dismiss() } + AddNewDeviceBottomSheet.NEW_DEVICE_REQUEST_CODE -> { + val user = app.telegramHelper.getCurrentUser() + if (user != null && data != null && data.hasExtra(AddNewDeviceBottomSheet.DEVICE_JSON)) { + val deviceJson = data.getStringExtra(AddNewDeviceBottomSheet.DEVICE_JSON) + val device = OsmandApiUtils.parseDeviceBot(JSONObject(deviceJson)) + if (device != null) { + app.settings.addShareDevice(device) + val inflater = activity?.layoutInflater + if (inflater != null) { + addItemToContainer(inflater, shareAsContainer, device.externalId, device.deviceName) + Toast.makeText(app, getString(R.string.device_added_successfully, device.deviceName), Toast.LENGTH_SHORT).show() + } + } + } + } } } @@ -167,7 +213,7 @@ class SettingsDialogFragment : BaseDialogFragment() { isChecked = checked } setOnClickListener { - settings.currentSharingMode = tag + settings.updateCurrentSharingMode(tag) updateSelectedSharingMode() } this.tag = tag @@ -182,7 +228,11 @@ class SettingsDialogFragment : BaseDialogFragment() { isModal = true anchorView = valueView setContentWidth(AndroidUtils.getPopupMenuWidth(ctx, menuList)) - height = AndroidUtils.getPopupMenuHeight(ctx) + height = if (menuList.size < 6) { + ListPopupWindow.WRAP_CONTENT + } else { + AndroidUtils.getPopupMenuHeight(ctx) + } setDropDownGravity(Gravity.END or Gravity.TOP) setAdapter(ArrayAdapter(ctx, R.layout.popup_list_text_item, menuList)) setOnItemClickListener { _, _, position, _ -> @@ -235,6 +285,46 @@ class SettingsDialogFragment : BaseDialogFragment() { } } + private fun updateShareAsDescription() { + if (shareAsDescriptionHidden) { + shareAsDescription.text = getFullShareAsDescriptionText() + } else { + shareAsDescription.text = getText(R.string.share_location_as_description) + } + shareAsDescriptionHidden = !shareAsDescriptionHidden + } + + private fun getFullShareAsDescriptionText(): CharSequence { + val textHide = "${getString(R.string.shared_string_hide)}." + val spannableString = SpannableStringBuilder(getText(R.string.share_location_as_description)) + val newSpannable = SpannableStringBuilder(getString(R.string.share_location_as_description_second_line, OSMAND_BOT_USERNAME, textHide)) + + spannableString.append("\n\n") + + var startIndex = newSpannable.indexOf(OSMAND_BOT_USERNAME) + var endIndex = startIndex + OSMAND_BOT_USERNAME.length + newSpannable.setSpan(ForegroundColorSpan(app.uiUtils.getActiveColor()), startIndex, endIndex, 0) + + startIndex = newSpannable.indexOf(textHide) + endIndex = startIndex + textHide.length + newSpannable.setSpan(ForegroundColorSpan(app.uiUtils.getActiveColor()), startIndex, endIndex, 0) + + spannableString.append(newSpannable) + + return spannableString + } + + private fun getAddNewDeviceIcon(): Drawable? { + val normal = app.uiUtils.getActiveIcon(R.drawable.ic_action_add) + if (Build.VERSION.SDK_INT >= 21) { + val active = app.uiUtils.getIcon(R.drawable.ic_action_add, R.color.ctrl_light) + if (normal != null && active != null) { + return AndroidUtils.createPressedStateListDrawable(normal, active) + } + } + return normal + } + private fun logoutTelegram() { val act = activity ?: return (act as MainActivity).logoutTelegram() diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/SharingStatusBottomSheet.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/SharingStatusBottomSheet.kt index 0708a2ff06..eaf2bc98b3 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/ui/SharingStatusBottomSheet.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/SharingStatusBottomSheet.kt @@ -55,16 +55,17 @@ class SharingStatusBottomSheet : DialogFragment() { findViewById(R.id.status_change_time).text = OsmandFormatter.getFormattedTime(sharingStatus.statusChangeTime, false) findViewById(R.id.last_location_line).text = sharingStatus.description - if (sharingStatusType != TelegramSettings.SharingStatusType.INITIALIZING - && sharingStatusType != TelegramSettings.SharingStatusType.SENDING) { - val descriptionTime = when { - time > 0 -> OsmandFormatter.getFormattedTime(time, false) - sharingStatusType == TelegramSettings.SharingStatusType.NO_GPS -> getString( - R.string.not_found_yet - ) - else -> getString(R.string.not_sent_yet) + if (sharingStatusType != TelegramSettings.SharingStatusType.INITIALIZING) { + if ((sharingStatusType == TelegramSettings.SharingStatusType.SENDING && time <= 0)) { + findViewById(R.id.last_location_line_time).visibility = View.GONE + } else { + val descriptionTime = when { + time > 0 -> OsmandFormatter.getFormattedTime(time, false) + sharingStatusType == TelegramSettings.SharingStatusType.NO_GPS -> getString(R.string.not_found_yet) + else -> getString(R.string.not_sent_yet) + } + findViewById(R.id.last_location_line_time).text = descriptionTime } - findViewById(R.id.last_location_line_time).text = descriptionTime } else { findViewById(R.id.last_location_line_time).visibility = View.GONE } diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/TimelineTabFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/TimelineTabFragment.kt new file mode 100644 index 0000000000..721b885056 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/TimelineTabFragment.kt @@ -0,0 +1,305 @@ +package net.osmand.telegram.ui + +import android.annotation.SuppressLint +import android.app.DatePickerDialog +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.support.annotation.DrawableRes +import android.support.v4.app.Fragment +import android.support.v4.widget.SwipeRefreshLayout +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.Switch +import android.widget.TextView +import net.osmand.PlatformUtil +import net.osmand.telegram.R +import net.osmand.telegram.TelegramApplication +import net.osmand.telegram.helpers.LocationMessages +import net.osmand.telegram.helpers.TelegramUiHelper +import net.osmand.telegram.helpers.TelegramUiHelper.ListItem +import net.osmand.telegram.ui.TimelineTabFragment.LiveNowListAdapter.BaseViewHolder +import net.osmand.telegram.utils.AndroidUtils +import net.osmand.telegram.utils.OsmandFormatter +import java.util.* + + +class TimelineTabFragment : Fragment() { + + private val log = PlatformUtil.getLog(TimelineTabFragment::class.java) + + private val app: TelegramApplication + get() = activity?.application as TelegramApplication + + private val telegramHelper get() = app.telegramHelper + private val settings get() = app.settings + + private lateinit var adapter: LiveNowListAdapter + + private lateinit var dateBtn: TextView + private lateinit var mainView: View + + private var start = 0L + private var end = 0L + + private var updateEnable: Boolean = false + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + mainView = inflater.inflate(R.layout.fragment_timeline_tab, container, false) + val appBarLayout = mainView.findViewById(R.id.app_bar_layout) + + val calendar = Calendar.getInstance() + start = getStartOfDay(calendar) + end = getEndOfDay(calendar) + + AndroidUtils.addStatusBarPadding19v(context!!, appBarLayout) + adapter = LiveNowListAdapter() + mainView.findViewById(R.id.recycler_view).apply { + layoutManager = LinearLayoutManager(context) + adapter = this@TimelineTabFragment.adapter + } + + val switcher = mainView.findViewById(R.id.monitoring_switcher) + val monitoringTv = mainView.findViewById(R.id.monitoring_title) + switcher.isChecked = settings.monitoringEnabled + monitoringTv.setText(if (settings.monitoringEnabled) R.string.monitoring_is_enabled else R.string.monitoring_is_disabled) + + mainView.findViewById(R.id.monitoring_container).setOnClickListener { + settings.monitoringEnabled = !settings.monitoringEnabled + app.showLocationHelper.changeUpdatesType() + switcher.isChecked = settings.monitoringEnabled + monitoringTv.setText(if (settings.monitoringEnabled) R.string.monitoring_is_enabled else R.string.monitoring_is_disabled) + } + + dateBtn = mainView.findViewById(R.id.date_btn).apply { + setOnClickListener { + selectDate() + } + setCompoundDrawablesWithIntrinsicBounds(getPressedStateIcon(R.drawable.ic_action_date_start), null, null, null) + setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light)) + text = OsmandFormatter.getFormattedDate(start / 1000) + } + + mainView.findViewById(R.id.swipe_refresh).apply { + setOnRefreshListener { + updateList() + isRefreshing = false + } + setColorSchemeColors(Color.RED, Color.GREEN, Color.BLUE, Color.CYAN) + } + updateList() + + return mainView + } + + override fun onResume() { + super.onResume() + updateEnable = true + startHandler() + } + + override fun onPause() { + super.onPause() + updateEnable = false + } + + private fun selectDate() { + val dateFromDialog = + DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth -> + val calendar = Calendar.getInstance() + calendar.set(Calendar.YEAR, year) + calendar.set(Calendar.MONTH, monthOfYear) + calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth) + + start = getStartOfDay(calendar) + end = getEndOfDay(calendar) + + updateList() + updateDateButton() + } + val startCalendar = Calendar.getInstance() + startCalendar.timeInMillis = start + DatePickerDialog(context, dateFromDialog, + startCalendar.get(Calendar.YEAR), + startCalendar.get(Calendar.MONTH), + startCalendar.get(Calendar.DAY_OF_MONTH) + ).show() + } + + private fun getStartOfDay(calendar: Calendar): Long { + calendar.set(Calendar.HOUR_OF_DAY, 0) + calendar.clear(Calendar.MINUTE) + calendar.clear(Calendar.SECOND) + calendar.clear(Calendar.MILLISECOND) + return calendar.timeInMillis + } + + private fun getEndOfDay(calendar: Calendar): Long { + calendar.set(Calendar.HOUR_OF_DAY, 23) + calendar.set(Calendar.MINUTE, 59) + calendar.set(Calendar.SECOND, 59) + calendar.set(Calendar.MILLISECOND, 999) + return calendar.timeInMillis + } + + private fun updateDateButton() { + dateBtn.text = OsmandFormatter.getFormattedDate(start / 1000) + } + + private fun getPressedStateIcon(@DrawableRes iconId: Int): Drawable? { + val normal = app.uiUtils.getActiveIcon(iconId) + if (Build.VERSION.SDK_INT >= 21) { + val active = app.uiUtils.getIcon(iconId, R.color.ctrl_light) + if (normal != null && active != null) { + return AndroidUtils.createPressedStateListDrawable(normal, active) + } + } + return normal + } + + private fun startHandler() { + val updateAdapter = Handler() + updateAdapter.postDelayed({ + if (updateEnable) { + updateList() + startHandler() + } + }, ADAPTER_UPDATE_INTERVAL_MIL) + } + + private fun updateList() { + val res = mutableListOf() + app.locationMessages.getIngoingUserLocations(start, end).forEach { + TelegramUiHelper.userLocationsToChatItem(telegramHelper, it)?.also { chatItem -> + res.add(chatItem) + } + } + adapter.items = sortAdapterItems(res) + } + + private fun sortAdapterItems(list: MutableList): MutableList { + val currentUserId = telegramHelper.getCurrentUserId() + list.sortWith(java.util.Comparator { lhs, rhs -> + when (currentUserId) { + lhs.userId -> return@Comparator -1 + rhs.userId -> return@Comparator 1 + else -> return@Comparator lhs.name.compareTo(rhs.name) + } + }) + return list + } + + inner class LiveNowListAdapter : RecyclerView.Adapter() { + + var items: List = emptyList() + set(value) { + field = value + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { + val inflater = LayoutInflater.from(parent.context) + return BaseViewHolder(inflater.inflate(R.layout.live_now_chat_card, parent, false)) + } + + @SuppressLint("SetTextI18n") + override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { + val lastItem = position == itemCount - 1 + val item = items[position] + val currentUserId = telegramHelper.getCurrentUserId() + TelegramUiHelper.setupPhoto(app, holder.icon, item.photoPath, R.drawable.img_user_picture_active, false) + holder.title?.text = item.name + holder.bottomShadow?.visibility = if (lastItem) View.VISIBLE else View.GONE + holder.lastTelegramUpdateTime?.visibility = View.GONE + + if (item is TelegramUiHelper.LocationMessagesChatItem) { + val userLocations = item.userLocations + if (userLocations != null) { + val trackData = getDistanceAndCountedPoints(userLocations) + val distance = OsmandFormatter.getFormattedDistance(trackData.dist, app) + val groupDescrRowVisible = (!item.privateChat || item.chatWithBot) && item.userId != currentUserId + if (groupDescrRowVisible) { + holder.groupDescrContainer?.visibility = View.VISIBLE + holder.groupTitle?.text = item.getVisibleName() + TelegramUiHelper.setupPhoto(app, holder.groupImage, item.groupPhotoPath, item.placeholderId, false) + } else { + holder.groupDescrContainer?.visibility = View.GONE + } + holder.locationAndDescrContainer?.visibility = View.GONE + holder.distanceAndPointsContainer?.visibility = View.VISIBLE + holder.distanceImage?.setImageDrawable(app.uiUtils.getThemedIcon(R.drawable.ic_action_distance_16dp)) + val point = if (groupDescrRowVisible) " • " else "" + holder.distanceAndPointsTitle?.text = "$distance (${getString(R.string.points_size, trackData.points)}) $point " + holder.userRow?.setOnClickListener { + childFragmentManager.also { + UserGpxInfoFragment.showInstance(it, item.userId, item.chatId, trackData.minTime, trackData.maxTime) + } + } + } + + holder.imageButton?.visibility = View.GONE + holder.showOnMapRow?.visibility = View.GONE + holder.bottomDivider?.visibility = if (lastItem) View.GONE else View.VISIBLE + holder.topDivider?.visibility = if (position != 0) View.GONE else View.VISIBLE + } + } + + + private fun getDistanceAndCountedPoints(userLocations: LocationMessages.UserLocations): UITrackData { + val uiTrackData = UITrackData(0.0f, 0, 0, 0) + + userLocations.getUniqueSegments().forEach { + if (uiTrackData.minTime == 0L) { + uiTrackData.minTime = it.minTime + } + uiTrackData.dist += it.distance.toFloat(); + uiTrackData.points += it.points.size + uiTrackData.maxTime = it.maxTime + } + return uiTrackData + } + + override fun getItemCount() = items.size + + inner class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val icon: ImageView? = view.findViewById(R.id.icon) + val title: TextView? = view.findViewById(R.id.title) + val description: TextView? = view.findViewById(R.id.description) + val bottomShadow: View? = view.findViewById(R.id.bottom_shadow) + val lastTelegramUpdateTime: TextView? = view.findViewById(R.id.last_telegram_update_time) + + val userRow: View? = view.findViewById(R.id.user_row) + val distanceAndPointsContainer: View? = view.findViewById(R.id.distance_and_points_container) + val locationAndDescrContainer: View? = view.findViewById(R.id.location_with_descr_container) + val distanceImage: ImageView? = view.findViewById(R.id.distance_icon) + val distanceAndPointsTitle: TextView? = view.findViewById(R.id.distance_and_points_text) + val groupDescrContainer: View? = view.findViewById(R.id.group_container) + val groupImage: ImageView? = view.findViewById(R.id.group_icon) + val groupTitle: TextView? = view.findViewById(R.id.group_title) + val imageButton: ImageView? = view.findViewById(R.id.image_button) + val showOnMapRow: View? = view.findViewById(R.id.show_on_map_row) + val topDivider: View? = view.findViewById(R.id.top_divider) + val bottomDivider: View? = view.findViewById(R.id.bottom_divider) + } + } + + data class UITrackData ( + var dist: Float, + var points: Int, + var minTime: Long, + var maxTime: Long + ) + companion object { + private const val ADAPTER_UPDATE_INTERVAL_MIL = 15 * 1000L // 15 sec + } +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/ui/UserGpxInfoFragment.kt b/OsmAnd-telegram/src/net/osmand/telegram/ui/UserGpxInfoFragment.kt new file mode 100644 index 0000000000..27c82e681c --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/ui/UserGpxInfoFragment.kt @@ -0,0 +1,397 @@ +package net.osmand.telegram.ui + +import android.app.DatePickerDialog +import android.app.TimePickerDialog +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import android.os.Build +import android.os.Bundle +import android.support.design.widget.Snackbar +import android.support.v4.app.FragmentManager +import android.util.DisplayMetrics +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import android.widget.Toast +import net.osmand.PlatformUtil +import net.osmand.aidl.gpx.AGpxBitmap +import net.osmand.telegram.R +import net.osmand.telegram.TelegramSettings +import net.osmand.telegram.helpers.LocationMessages +import net.osmand.telegram.helpers.OsmandAidlHelper +import net.osmand.telegram.helpers.TelegramUiHelper +import net.osmand.telegram.utils.* +import net.osmand.util.Algorithms +import java.io.File +import java.text.SimpleDateFormat +import java.util.* + +class UserGpxInfoFragment : BaseDialogFragment() { + + private val log = PlatformUtil.getLog(UserGpxInfoFragment::class.java) + + private val uiUtils get() = app.uiUtils + + private var gpxFile = GPXUtilities.GPXFile() + + private lateinit var mainView: View + private lateinit var dateStartBtn: TextView + private lateinit var timeStartBtn: TextView + private lateinit var dateEndBtn: TextView + private lateinit var timeEndBtn: TextView + + private lateinit var avgElevationTv: TextView + private lateinit var avgSpeedTv: TextView + private lateinit var totalDistanceTv: TextView + private lateinit var timeSpanTv: TextView + + private var startCalendar = Calendar.getInstance() + private var endCalendar = Calendar.getInstance() + + private var locationMessages = emptyList() + + private var userId = -1 + private var chatId = -1L + + override fun onCreateView( + inflater: LayoutInflater, + parent: ViewGroup?, + savedInstanceState: Bundle? + ): View { + mainView = inflater.inflate(R.layout.fragment_user_gpx_info, parent) + AndroidUtils.addStatusBarPadding19v(context!!, mainView) + + readFromBundle(savedInstanceState ?: arguments) + + val user = app.telegramHelper.getUser(userId) + if (user != null) { + mainView.findViewById(R.id.title).text = TelegramUiHelper.getUserName(user) + TelegramUiHelper.setupPhoto(app, mainView.findViewById(R.id.user_icon), + telegramHelper.getUserPhotoPath(user), R.drawable.img_user_placeholder, false) + } + val openGpxListener = View.OnClickListener { + val gpx = gpxFile + if (gpx.path.isNotEmpty()) { + openGpx(gpx.path) + } else { + saveCurrentGpxToFile(object : + OsmandLocationUtils.SaveGpxListener { + + override fun onSavingGpxFinish(path: String) { + openGpx(path) + } + + override fun onSavingGpxError(warnings: List?) { + Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show() + } + }) + } + } + val iconMap = mainView.findViewById(R.id.gpx_map) + app.osmandAidlHelper.setGpxBitmapCreatedListener( + object : OsmandAidlHelper.GpxBitmapCreatedListener { + override fun onGpxBitmapCreated(bitmap: AGpxBitmap) { + activity?.runOnUiThread { + iconMap.setImageDrawable(BitmapDrawable(app.resources, bitmap.bitmap)) + iconMap.setOnClickListener(openGpxListener) + } + } + }) + + mainView.findViewById(R.id.back_button).apply { + setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_arrow_back)) + setOnClickListener { + dismiss() + } + } + + dateStartBtn = mainView.findViewById(R.id.date_start_btn) + timeStartBtn = mainView.findViewById(R.id.time_start_btn) + dateEndBtn = mainView.findViewById(R.id.date_end_btn) + timeEndBtn = mainView.findViewById(R.id.time_end_btn) + + dateStartBtn.setOnClickListener { selectStartDate() } + timeStartBtn.setOnClickListener { selectStartTime() } + dateEndBtn.setOnClickListener { selectEndDate() } + timeEndBtn.setOnClickListener { selectEndTime() } + + setupBtnTextColor(dateStartBtn) + setupBtnTextColor(timeStartBtn) + setupBtnTextColor(dateEndBtn) + setupBtnTextColor(timeEndBtn) + + updateDateAndTimeButtons() + + avgElevationTv = mainView.findViewById(R.id.average_altitude_text) + avgSpeedTv = mainView.findViewById(R.id.average_speed_text) + totalDistanceTv = mainView.findViewById(R.id.distance_text) + timeSpanTv = mainView.findViewById(R.id.duration_text) + + mainView.findViewById(R.id.average_altitude_icon).apply { + setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_altitude_range)) + } + mainView.findViewById(R.id.average_speed_icon).apply { + setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_speed_average)) + } + mainView.findViewById(R.id.distance_icon).apply { + setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_sort_by_distance)) + } + mainView.findViewById(R.id.duration_icon).apply { + setImageDrawable(uiUtils.getThemedIcon(R.drawable.ic_action_time_span)) + } + + updateGPXStatisticRow() + + val imageRes = if (app.isOsmAndInstalled()) { + TelegramSettings.AppConnect.getIconId(settings.appToConnectPackage) + } else { + R.drawable.ic_logo_osmand_free + } + mainView.findViewById(R.id.open_in_osmand_icon).setImageResource(imageRes) + mainView.findViewById(R.id.open_in_osmand_btn).setOnClickListener(openGpxListener) + + mainView.findViewById(R.id.open_in_osmand_title).setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.primary_text_light, R.color.ctrl_light)) + mainView.findViewById(R.id.share_gpx_title).setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.primary_text_light, R.color.ctrl_light)) + + mainView.findViewById(R.id.share_gpx_icon).setImageDrawable(getShareIcon()) + mainView.findViewById(R.id.share_gpx_btn).apply { + setOnClickListener { + val gpx = gpxFile + if (gpx.path.isNotEmpty()) { + (activity as MainActivity).shareGpx(gpx.path) + } else { + saveCurrentGpxToFile(object : + OsmandLocationUtils.SaveGpxListener { + override fun onSavingGpxFinish(path: String) { + (activity as MainActivity).shareGpx(path) + } + + override fun onSavingGpxError(warnings: List?) { + Toast.makeText(app, warnings?.firstOrNull(), Toast.LENGTH_LONG).show() + } + }) + } + } + } + + updateGpxInfo() + + return mainView + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putLong(START_KEY, startCalendar.timeInMillis) + outState.putLong(END_KEY, endCalendar.timeInMillis) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + ChooseOsmAndBottomSheet.OSMAND_CHOSEN_REQUEST_CODE -> updateGPXMap() + } + } + + private fun canOsmandCreateBitmap(): Boolean { + val version = AndroidUtils.getAppVersionCode(app, app.settings.appToConnectPackage) + return version >= MIN_OSMAND_BITMAP_VERSION_CODE + } + + private fun openGpx(path: String) { + val fileUri = AndroidUtils.getUriForFile(app, File(path)) + val openGpxIntent = Intent(Intent.ACTION_VIEW) + openGpxIntent.setDataAndType(fileUri, "application/gpx+xml") + openGpxIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + val resolved = activity?.packageManager?.resolveActivity(openGpxIntent, PackageManager.MATCH_DEFAULT_ONLY) + if (resolved != null) { + startActivity(openGpxIntent) + } + } + + private fun saveCurrentGpxToFile(listener: OsmandLocationUtils.SaveGpxListener) { + if (!gpxFile.isEmpty) { + val file = File(app.getExternalFilesDir(null), TRACKS_DIR) + OsmandLocationUtils.saveGpx(app, listener, file, gpxFile) + } + } + + private fun readFromBundle(bundle: Bundle?) { + bundle?.also { + userId = it.getInt(USER_ID_KEY) + chatId = it.getLong(CHAT_ID_KEY) + startCalendar.timeInMillis = it.getLong(START_KEY) + endCalendar.timeInMillis = it.getLong(END_KEY) + } + } + + private fun setupBtnTextColor(textView: TextView) { + textView.setTextColor(AndroidUtils.createPressedColorStateList(app, true, R.color.ctrl_active_light, R.color.ctrl_light)) + } + + private fun getShareIcon(): Drawable? { + val normal = app.uiUtils.getActiveIcon(R.drawable.ic_action_share) + if (Build.VERSION.SDK_INT >= 21) { + val active = app.uiUtils.getIcon(R.drawable.ic_action_share, R.color.ctrl_light) + if (normal != null && active != null) { + return AndroidUtils.createPressedStateListDrawable(normal, active) + } + } + return normal + } + + private fun updateGpxInfo() { + checkTime() + locationMessages = app.locationMessages.getMessagesForUserInChat(userId, chatId, startCalendar.timeInMillis, endCalendar.timeInMillis) + + gpxFile = OsmandLocationUtils.convertLocationMessagesToGpxFiles(locationMessages).firstOrNull()?:GPXUtilities.GPXFile() + updateGPXStatisticRow() + updateDateAndTimeButtons() + updateGPXMap() + } + + private fun checkTime() { + if (startCalendar.timeInMillis > endCalendar.timeInMillis) { + val time = startCalendar.timeInMillis + startCalendar.timeInMillis = endCalendar.timeInMillis + endCalendar.timeInMillis = time + } + } + + private fun updateDateAndTimeButtons() { + dateStartBtn.text = SimpleDateFormat("dd MMM", Locale.getDefault()).format(startCalendar.timeInMillis) + dateEndBtn.text = SimpleDateFormat("dd MMM", Locale.getDefault()).format(endCalendar.timeInMillis) + + timeStartBtn.text = SimpleDateFormat("HH:mm", Locale.getDefault()).format(startCalendar.timeInMillis) + timeEndBtn.text = SimpleDateFormat("HH:mm", Locale.getDefault()).format(endCalendar.timeInMillis) + } + + private fun updateGPXStatisticRow() { + val analysis: GPXUtilities.GPXTrackAnalysis = gpxFile.getAnalysis(0) + + avgElevationTv.text = if (analysis.avgElevation != 0.0) OsmandFormatter.getFormattedAlt(analysis.avgElevation, app) else "-" + avgSpeedTv.text = if (analysis.isSpeedSpecified) OsmandFormatter.getFormattedSpeed(analysis.avgSpeed, app) else "-" + totalDistanceTv.text = if (analysis.totalDistance != 0.0f) OsmandFormatter.getFormattedDistance(analysis.totalDistance, app) else "-" + timeSpanTv.text = if (analysis.timeSpan != 0L) Algorithms.formatDuration((analysis.timeSpan / 1000).toInt(), true) else "-" + } + + private fun updateGPXMap() { + if (!app.isAnyOsmAndInstalled()) { + activity?.let { + MainActivity.OsmandMissingDialogFragment().show(it.supportFragmentManager, null) + } + } else if (!app.isOsmAndChosen() || (app.isOsmAndChosen() && !app.isOsmAndInstalled())) { + fragmentManager?.also { ChooseOsmAndBottomSheet.showInstance(it, this) } + } else if (!canOsmandCreateBitmap()) { + val snackbar = Snackbar.make(mainView, R.string.please_update_osmand, Snackbar.LENGTH_LONG).setAction(R.string.shared_string_update) { + val packageName = if (app.settings.appToConnectPackage == OsmandAidlHelper.OSMAND_NIGHTLY_PACKAGE_NAME) + OsmandAidlHelper.OSMAND_FREE_PACKAGE_NAME else app.settings.appToConnectPackage + startActivity(AndroidUtils.getPlayMarketIntent(app, packageName)) + } + AndroidUtils.setSnackbarTextColor(snackbar, R.color.ctrl_active_dark) + snackbar.show() + } else { + saveCurrentGpxToFile(object : + OsmandLocationUtils.SaveGpxListener { + override fun onSavingGpxFinish(path: String) { + val mgr = activity?.getSystemService(Context.WINDOW_SERVICE) + if (mgr != null) { + val dm = DisplayMetrics() + (mgr as WindowManager).defaultDisplay.getMetrics(dm) + val widthPixels = dm.widthPixels - (2 * app.resources.getDimensionPixelSize(R.dimen.content_padding_standard)) + val heightPixels = AndroidUtils.dpToPx(app, 152f) + val gpxUri = AndroidUtils.getUriForFile(app, File(path)) + app.osmandAidlHelper.execOsmandApi { + app.osmandAidlHelper.getBitmapForGpx(gpxUri, dm.density, widthPixels, heightPixels, GPX_TRACK_COLOR) + } + } + } + + override fun onSavingGpxError(warnings: List?) { + log.debug("onSavingGpxError ${warnings?.firstOrNull()}") + } + }) + } + } + + private fun selectStartDate() { + val dateFromDialog = + DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth -> + startCalendar.set(Calendar.YEAR, year) + startCalendar.set(Calendar.MONTH, monthOfYear) + startCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth) + updateGpxInfo() + } + DatePickerDialog(context, dateFromDialog, + startCalendar.get(Calendar.YEAR), + startCalendar.get(Calendar.MONTH), + startCalendar.get(Calendar.DAY_OF_MONTH)).show() + } + + private fun selectStartTime() { + TimePickerDialog(context, + TimePickerDialog.OnTimeSetListener { _, hours, minutes -> + startCalendar.set(Calendar.HOUR_OF_DAY, hours) + startCalendar.set(Calendar.MINUTE, minutes) + updateGpxInfo() + }, 0, 0, true).show() + } + + private fun selectEndDate() { + val dateFromDialog = + DatePickerDialog.OnDateSetListener { _, year, monthOfYear, dayOfMonth -> + endCalendar.set(Calendar.YEAR, year) + endCalendar.set(Calendar.MONTH, monthOfYear) + endCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth) + updateGpxInfo() + } + DatePickerDialog(context, dateFromDialog, + endCalendar.get(Calendar.YEAR), + endCalendar.get(Calendar.MONTH), + endCalendar.get(Calendar.DAY_OF_MONTH)).show() + } + + private fun selectEndTime() { + TimePickerDialog(context, + TimePickerDialog.OnTimeSetListener { _, hours, minutes -> + endCalendar.set(Calendar.HOUR_OF_DAY, hours) + endCalendar.set(Calendar.MINUTE, minutes) + updateGpxInfo() + }, 0, 0, true).show() + } + + companion object { + + private const val TAG = "UserGpxInfoFragment" + private const val START_KEY = "start_key" + private const val END_KEY = "end_key" + private const val USER_ID_KEY = "user_id_key" + private const val CHAT_ID_KEY = "chat_id_key" + + private const val GPX_TRACK_COLOR = -65536 + private const val MIN_OSMAND_BITMAP_VERSION_CODE = 330 + + fun showInstance(fm: FragmentManager,userId:Int,chatId:Long, start: Long, end: Long): Boolean { + return try { + val fragment = UserGpxInfoFragment().apply { + arguments = Bundle().apply { + putInt(USER_ID_KEY, userId) + putLong(CHAT_ID_KEY, chatId) + putLong(START_KEY, start) + putLong(END_KEY, end) + } + } + fragment.show(fm, TAG) + true + } catch (e: RuntimeException) { + false + } + } + } +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidNetworkUtils.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidNetworkUtils.kt index 928ccc4905..3b5f99a974 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidNetworkUtils.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidNetworkUtils.kt @@ -2,8 +2,14 @@ package net.osmand.telegram.utils import android.os.AsyncTask import net.osmand.PlatformUtil -import java.io.* -import java.net.* +import net.osmand.telegram.TelegramApplication +import java.io.BufferedOutputStream +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.MalformedURLException +import java.net.URL object AndroidNetworkUtils { @@ -11,21 +17,34 @@ object AndroidNetworkUtils { private val log = PlatformUtil.getLog(AndroidNetworkUtils::class.java) interface OnRequestResultListener { - fun onResult(result: String) + fun onResult(result: String?) } - fun sendRequestAsync(urlText: String, listener: OnRequestResultListener?) { - SendRequestTask(urlText, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + fun sendRequestAsync( + app: TelegramApplication, + urlText: String, + json: String?, + userOperation: String, + toastAllowed: Boolean, + post: Boolean, + listener: OnRequestResultListener? + ) { + SendRequestTask(app, urlText, json, userOperation, toastAllowed, post, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) } private class SendRequestTask( - private val urlText: String, + private val app: TelegramApplication, + private val url: String, + private val json: String?, + private val userOperation: String, + private val toastAllowed: Boolean, + private val post: Boolean, private val listener: OnRequestResultListener? ) : AsyncTask() { override fun doInBackground(vararg params: Void): String? { return try { - sendRequest(urlText) + sendRequest(app, url, json, userOperation, toastAllowed, post) } catch (e: Exception) { log.error(e.message, e) null @@ -33,47 +52,99 @@ object AndroidNetworkUtils { } override fun onPostExecute(response: String?) { - if (response != null) { - listener?.onResult(response) - } + listener?.onResult(response) } } - fun sendRequest(urlText: String): String? { + fun sendRequest( + app: TelegramApplication, + url: String, + jsonBody: String?, + userOperation: String, + toastAllowed: Boolean, + post: Boolean + ): String? { + var connection: HttpURLConnection? = null try { - log.info("GET : $urlText") - val conn = getHttpURLConnection(urlText) - conn.doInput = true - conn.doOutput = false - conn.requestMethod = "GET" - conn.setRequestProperty("User-Agent", "OsmAnd Sharing") - log.info("Response code and message : " + conn.responseCode + " " + conn.responseMessage) - if (conn.responseCode != 200) { - return conn.responseMessage + connection = getHttpURLConnection(url) + connection.setRequestProperty("Accept-Charset", "UTF-8") + connection.setRequestProperty("User-Agent", app.packageName) + connection.connectTimeout = 15000 + if (jsonBody != null && post) { + connection.doInput = true + connection.doOutput = true + connection.useCaches = false + connection.requestMethod = "POST" + + connection.setRequestProperty("Accept", "application/json") + connection.setRequestProperty("Content-Type", "application/json") + connection.setRequestProperty("Content-Length", jsonBody.toByteArray(charset("UTF-8")).size.toString()) + + connection.setFixedLengthStreamingMode(jsonBody.toByteArray(charset("UTF-8")).size) + + val output = BufferedOutputStream(connection.outputStream) + + output.write(jsonBody.toByteArray(charset("UTF-8"))) + output.flush() + output.close() + + } else { + connection.requestMethod = "GET" + connection.connect() } - val inputStream = conn.inputStream - val responseBody = StringBuilder() - responseBody.setLength(0) - if (inputStream != null) { - val bufferedInput = BufferedReader(InputStreamReader(inputStream, "UTF-8")) - var s = bufferedInput.readLine() - var first = true - while (s != null) { - if (first) { - first = false - } else { - responseBody.append("\n") - } - responseBody.append(s) - s = bufferedInput.readLine() + + if (connection.responseCode != HttpURLConnection.HTTP_OK) { + if (toastAllowed) { + val msg = (userOperation + " " + "Failed: " + connection.responseMessage) + log.error(msg) } - inputStream.close() + } else { + val responseBody = StringBuilder() + responseBody.setLength(0) + val i = connection.inputStream + if (i != null) { + val input = BufferedReader(InputStreamReader(i, "UTF-8"), 256) + var s: String? = input.readLine() + var f = true + while (s != null) { + if (!f) { + responseBody.append("\n") + } else { + f = false + } + responseBody.append(s) + s = input.readLine() + } + try { + input.close() + i.close() + } catch (e: Exception) { + // ignore exception + } + + } + return responseBody.toString() + } + } catch (e: NullPointerException) { + if (toastAllowed) { + val msg = (userOperation + " " + "Failed - $e" + ": " + connection?.responseMessage) + log.error(msg) + } + } catch (e: MalformedURLException) { + if (toastAllowed) { + val msg = (userOperation + " " + "Failed - $e" + ": " + connection?.responseMessage) + log.error(msg) } - return responseBody.toString() } catch (e: IOException) { - log.error(e.message, e) - return e.message + if (toastAllowed) { + val msg = (userOperation + " " + "Failed - $e" + ": " + connection?.responseMessage) + log.error(msg) + } + } finally { + connection?.disconnect() } + + return null } @Throws(MalformedURLException::class, IOException::class) diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt index a205a55c55..858278976a 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/AndroidUtils.kt @@ -6,22 +6,29 @@ import android.content.ContentResolver import android.content.Context import android.content.Intent import android.content.pm.PackageManager +import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Color import android.graphics.Paint +import android.graphics.drawable.Drawable +import android.graphics.drawable.StateListDrawable import android.net.Uri import android.os.Build import android.support.annotation.AttrRes import android.support.annotation.ColorInt +import android.support.annotation.ColorRes +import android.support.design.widget.Snackbar import android.support.v4.app.ActivityCompat import android.support.v4.app.DialogFragment import android.support.v4.app.FragmentManager +import android.support.v4.content.ContextCompat import android.support.v4.content.FileProvider import android.util.TypedValue import android.util.TypedValue.COMPLEX_UNIT_DIP import android.view.View import android.view.WindowManager import android.view.inputmethod.InputMethodManager +import android.widget.TextView import net.osmand.telegram.R import java.io.File @@ -131,6 +138,52 @@ object AndroidUtils { return ctx.resources.getDimensionPixelSize(R.dimen.list_popup_window_height) } + fun setSnackbarTextColor(snackbar: Snackbar, @ColorRes colorId: Int) { + val view = snackbar.view + val tv = view.findViewById(android.support.design.R.id.snackbar_action) as TextView + tv.setTextColor(ContextCompat.getColor(view.context, colorId)) + } + + fun createPressedColorStateList( + ctx: Context, light: Boolean, + @ColorRes lightNormal: Int, @ColorRes lightPressed: Int, + @ColorRes darkNormal: Int = 0, @ColorRes darkPressed: Int = 0 + ): ColorStateList { + return createColorStateList( + ctx, light, android.R.attr.state_pressed, + lightNormal, lightPressed, darkNormal, darkPressed + ) + } + + fun createColorStateList( + ctx: Context, light: Boolean, state: Int, + @ColorRes lightNormal: Int, @ColorRes lightState: Int, + @ColorRes darkNormal: Int, @ColorRes darkState: Int + ): ColorStateList { + return ColorStateList( + arrayOf(intArrayOf(state), intArrayOf()), + intArrayOf( + ContextCompat.getColor(ctx, if (light) lightState else darkState), + ContextCompat.getColor(ctx, if (light) lightNormal else darkNormal) + ) + ) + } + + fun createPressedStateListDrawable(normal: Drawable, pressed: Drawable): StateListDrawable { + return createStateListDrawable(normal, pressed, android.R.attr.state_pressed) + } + + fun createStateListDrawable( + normal: Drawable, + stateDrawable: Drawable, + state: Int + ): StateListDrawable { + val res = StateListDrawable() + res.addState(intArrayOf(state), stateDrawable) + res.addState(intArrayOf(), normal) + return res + } + @ColorInt fun getAttrColor(ctx: Context, @AttrRes attrId: Int, @ColorInt defaultColor: Int = 0): Int { val ta = ctx.theme.obtainStyledAttributes(intArrayOf(attrId)) diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/GPXUtilities.java b/OsmAnd-telegram/src/net/osmand/telegram/utils/GPXUtilities.java new file mode 100644 index 0000000000..b27293c40b --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/GPXUtilities.java @@ -0,0 +1,1808 @@ +package net.osmand.telegram.utils; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.support.annotation.ColorInt; +import android.support.annotation.Nullable; +import android.text.TextUtils; + +import net.osmand.Location; +import net.osmand.PlatformUtil; +import net.osmand.data.QuadRect; +import net.osmand.data.RotatedTileBox; +import net.osmand.telegram.TelegramApplication; +import net.osmand.util.Algorithms; + +import org.apache.commons.logging.Log; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.StringWriter; +import java.io.Writer; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.NumberFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import java.util.TimeZone; + +// copy from net.osmand.plus.GPXUtilities and changes done to WptPt and GPXFile (userId,chatId) + +public class GPXUtilities { + public final static Log log = PlatformUtil.getLog(GPXUtilities.class); + + private final static String GPX_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; //$NON-NLS-1$ + private final static String GPX_TIME_FORMAT_MILLIS = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; //$NON-NLS-1$ + + private final static NumberFormat latLonFormat = new DecimalFormat("0.00#####", new DecimalFormatSymbols( + new Locale("EN", "US"))); + private final static NumberFormat decimalFormat = new DecimalFormat("#.###", new DecimalFormatSymbols( + new Locale("EN", "US"))); + + public static class GPXExtensions { + Map extensions = null; + + public Map getExtensionsToRead() { + if (extensions == null) { + return Collections.emptyMap(); + } + return extensions; + } + + @ColorInt + public int getColor(@ColorInt int defColor) { + String clrValue = null; + if (extensions != null) { + clrValue = extensions.get("color"); + if (clrValue == null) { + clrValue = extensions.get("colour"); + } + if (clrValue == null) { + clrValue = extensions.get("displaycolor"); + } + } + if (clrValue != null && clrValue.length() > 0) { + try { + return Color.parseColor(clrValue.toUpperCase()); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } + } + return defColor; + } + + public void setColor(int color) { + getExtensionsToWrite().put("color", Algorithms.colorToString(color)); + } + + public void removeColor() { + getExtensionsToWrite().remove("color"); + } + + public Map getExtensionsToWrite() { + if (extensions == null) { + extensions = new LinkedHashMap<>(); + } + return extensions; + } + + } + + public static class Elevation { + public float distance; + public int time; + public float elevation; + } + + public static class Speed { + public float distance; + public int time; + public float speed; + } + + public static class WptPt extends GPXExtensions implements LocationPoint { + public boolean firstPoint = false; + public boolean lastPoint = false; + public int userId; + public long chatId; + public double lat; + public double lon; + public String name = null; + public String link = null; + // previous undocumented feature 'category' ,now 'type' + public String category = null; + public String desc = null; + public String comment = null; + // by default + public long time = 0; + public double ele = Double.NaN; + public double speed = 0; + public double hdop = Double.NaN; + public boolean deleted = false; + public int colourARGB = 0; // point colour (used for altitude/speed colouring) + public double distance = 0.0; // cumulative distance, if in a track + + public WptPt() { + } + + public WptPt(WptPt wptPt) { + this.lat = wptPt.lat; + this.lon = wptPt.lon; + this.name = wptPt.name; + this.link = wptPt.link; + + this.category = wptPt.category; + this.desc = wptPt.desc; + this.comment = wptPt.comment; + + this.time = wptPt.time; + this.ele = wptPt.ele; + this.speed = wptPt.speed; + this.hdop = wptPt.hdop; + this.deleted = wptPt.deleted; + this.colourARGB = wptPt.colourARGB; + this.distance = wptPt.distance; + } + + public void setDistance(double dist) { + distance = dist; + } + + public double getDistance() { + return distance; + } + + @Override + public int getColor() { + return getColor(0); + } + + @Override + public double getLatitude() { + return lat; + } + + @Override + public double getLongitude() { + return lon; + } + + +// @Override +// public PointDescription getPointDescription(Context ctx) { +// return new PointDescription(PointDescription.POINT_TYPE_WPT, name); +// } + + public WptPt(double lat, double lon, long time, double ele, double speed, double hdop) { + this.lat = lat; + this.lon = lon; + this.time = time; + this.ele = ele; + this.speed = speed; + this.hdop = hdop; + } + + @Override + public boolean isVisible() { + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((category == null) ? 0 : category.hashCode()); + result = prime * result + ((desc == null) ? 0 : desc.hashCode()); + result = prime * result + ((comment == null) ? 0 : comment.hashCode()); + result = prime * result + ((lat == 0) ? 0 : Double.valueOf(lat).hashCode()); + result = prime * result + ((lon == 0) ? 0 : Double.valueOf(lon).hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + WptPt other = (WptPt) obj; + return Algorithms.objectEquals(other.name, name) + && Algorithms.objectEquals(other.category, category) + && Algorithms.objectEquals(other.lat, lat) + && Algorithms.objectEquals(other.lon, lon) + && Algorithms.objectEquals(other.desc, desc); + } + } + + public static class TrkSegment extends GPXExtensions { + public boolean generalSegment = false; + + public List points = new ArrayList<>(); + +// public List renders = new ArrayList<>(); + + public List splitByDistance(double meters) { + return split(getDistanceMetric(), getTimeSplit(), meters); + } + + public List splitByTime(int seconds) { + return split(getTimeSplit(), getDistanceMetric(), seconds); + } + + private List split(SplitMetric metric, SplitMetric secondaryMetric, double metricLimit) { + List splitSegments = new ArrayList<>(); + splitSegment(metric, secondaryMetric, metricLimit, splitSegments, this); + return convert(splitSegments); + } + +// public void drawRenderers(double zoom, Paint p, Canvas c, RotatedTileBox tb) { +// for (Renderable.RenderableSegment rs : renders) { +// rs.drawSegment(zoom, p, c, tb); +// } +// } + } + + public static class Track extends GPXExtensions { + public String name = null; + public String desc = null; + public List segments = new ArrayList<>(); + public boolean generalTrack = false; + + } + + public static class Route extends GPXExtensions { + public String name = null; + public String desc = null; + public List points = new ArrayList<>(); + + } + + public static class Metadata extends GPXExtensions { + public String desc; + + @Nullable + public String getArticleTitle() { + return getExtensionsToRead().get("article_title"); + } + + @Nullable + public String getArticleLang() { + return getExtensionsToRead().get("article_lang"); + } + } + + public static class GPXTrackAnalysis { + public float totalDistance = 0; + public int totalTracks = 0; + public long startTime = Long.MAX_VALUE; + public long endTime = Long.MIN_VALUE; + public long timeSpan = 0; + //Next few lines for Issue 3222 heuristic testing only + //public long timeMoving0 = 0; + //public float totalDistanceMoving0 = 0; + public long timeMoving = 0; + public float totalDistanceMoving = 0; + + public double diffElevationUp = 0; + public double diffElevationDown = 0; + public double avgElevation = 0; + public double minElevation = 99999; + public double maxElevation = -100; + + public float minSpeed = Float.MAX_VALUE; + public float maxSpeed = 0; + public float avgSpeed; + + public int points; + public int wptPoints = 0; + + public Set wptCategoryNames; + + public double metricEnd; + public double secondaryMetricEnd; + public WptPt locationStart; + public WptPt locationEnd; + + public double left = 0; + public double right = 0; + public double top = 0; + public double bottom = 0; + + public boolean isTimeSpecified() { + return startTime != Long.MAX_VALUE && startTime != 0; + } + + public boolean isTimeMoving() { + return timeMoving != 0; + } + + public boolean isElevationSpecified() { + return maxElevation != -100; + } + + public boolean hasSpeedInTrack() { + return hasSpeedInTrack; + } + + public boolean isBoundsCalculated() { + return left != 0 && right != 0 && top != 0 && bottom != 0; + } + + public List elevationData; + public List speedData; + + public boolean hasElevationData; + public boolean hasSpeedData; + public boolean hasSpeedInTrack = false; + + public boolean isSpeedSpecified() { + return avgSpeed > 0; + } + + + public static GPXTrackAnalysis segment(long filetimestamp, TrkSegment segment) { + return new GPXTrackAnalysis().prepareInformation(filetimestamp, new SplitSegment(segment)); + } + + public GPXTrackAnalysis prepareInformation(long filestamp, SplitSegment... splitSegments) { + float[] calculations = new float[1]; + + long startTimeOfSingleSegment = 0; + long endTimeOfSingleSegment = 0; + + float totalElevation = 0; + int elevationPoints = 0; + int speedCount = 0; + int timeDiff = 0; + double totalSpeedSum = 0; + points = 0; + + double channelThresMin = 10; // Minimum oscillation amplitude considered as relevant or as above noise for accumulated Ascent/Descent analysis + double channelThres = channelThresMin; // Actual oscillation amplitude considered as above noise (dynamic channel adjustment, accomodates depedency on current VDOP/getAccuracy if desired) + double channelBase; + double channelTop; + double channelBottom; + boolean climb = false; + + elevationData = new ArrayList<>(); + speedData = new ArrayList<>(); + + for (SplitSegment s : splitSegments) { + final int numberOfPoints = s.getNumberOfPoints(); + + channelBase = 99999; + channelTop = channelBase; + channelBottom = channelBase; + //channelThres = channelThresMin; //only for dynamic channel adjustment + + float segmentDistance = 0f; + metricEnd += s.metricEnd; + secondaryMetricEnd += s.secondaryMetricEnd; + points += numberOfPoints; + for (int j = 0; j < numberOfPoints; j++) { + WptPt point = s.get(j); + if (j == 0 && locationStart == null) { + locationStart = point; + } + if (j == numberOfPoints - 1) { + locationEnd = point; + } + long time = point.time; + if (time != 0) { + if (s.metricEnd == 0) { + if (s.segment.generalSegment) { + if (point.firstPoint) { + startTimeOfSingleSegment = time; + } else if (point.lastPoint) { + endTimeOfSingleSegment = time; + } + if (startTimeOfSingleSegment != 0 && endTimeOfSingleSegment != 0) { + timeSpan += endTimeOfSingleSegment - startTimeOfSingleSegment; + startTimeOfSingleSegment = 0; + endTimeOfSingleSegment = 0; + } + } + } + startTime = Math.min(startTime, time); + endTime = Math.max(endTime, time); + } + + if (left == 0 && right == 0) { + left = point.getLongitude(); + right = point.getLongitude(); + top = point.getLatitude(); + bottom = point.getLatitude(); + } else { + left = Math.min(left, point.getLongitude()); + right = Math.max(right, point.getLongitude()); + top = Math.max(top, point.getLatitude()); + bottom = Math.min(bottom, point.getLatitude()); + } + + double elevation = point.ele; + Elevation elevation1 = new Elevation(); + if (!Double.isNaN(elevation)) { + totalElevation += elevation; + elevationPoints++; + minElevation = Math.min(elevation, minElevation); + maxElevation = Math.max(elevation, maxElevation); + + elevation1.elevation = (float) elevation; + } else { + elevation1.elevation = Float.NaN; + } + + float speed = (float) point.speed; + if (speed > 0) { + hasSpeedInTrack = true; + } + + // Trend channel analysis for elevation gain/loss, Hardy 2015-09-22, LPF filtering added 2017-10-26: + // - Detect the consecutive elevation trend channels: Only use the net elevation changes of each trend channel (i.e. between the turnarounds) to accumulate the Ascent/Descent values. + // - Perform the channel evaluation on Low Pass Filter (LPF) smoothed ele data instead of on the raw ele data + // Parameters: + // - channelThresMin (in meters): defines the channel turnaround detection, i.e. oscillations smaller than this are ignored as irrelevant or noise. + // - smoothWindow (number of points): is the LPF window + // NOW REMOVED, as no relevant examples found: Dynamic channel adjustment: To suppress unreliable measurement points, could relax the turnaround detection from the constant channelThresMin to channelThres which is e.g. based on the maximum VDOP of any point which contributed to the current trend. (Good assumption is VDOP=2*HDOP, which accounts for invisibility of lower hemisphere satellites.) + + // LPF smooting of ele data, usually smooth over odd number of values like 5 + final int smoothWindow = 5; + double eleSmoothed = Double.NaN; + int j2 = 0; + for (int j1 = -smoothWindow + 1; j1 <= 0; j1++) { + if ((j + j1 >= 0) && !Double.isNaN(s.get(j + j1).ele)) { + j2++; + if (!Double.isNaN(eleSmoothed)) { + eleSmoothed = eleSmoothed + s.get(j + j1).ele; + } else { + eleSmoothed = s.get(j + j1).ele; + } + } + } + if (!Double.isNaN(eleSmoothed)) { + eleSmoothed = eleSmoothed / j2; + } + + if (!Double.isNaN(eleSmoothed)) { + // Init channel + if (channelBase == 99999) { + channelBase = eleSmoothed; + channelTop = channelBase; + channelBottom = channelBase; + //channelThres = channelThresMin; //only for dynamic channel adjustment + } + // Channel maintenance + if (eleSmoothed > channelTop) { + channelTop = eleSmoothed; + //if (!Double.isNaN(point.hdop)) { + // channelThres = Math.max(channelThres, 2.0 * point.hdop); //only for dynamic channel adjustment + //} + } else if (eleSmoothed < channelBottom) { + channelBottom = eleSmoothed; + //if (!Double.isNaN(point.hdop)) { + // channelThres = Math.max(channelThres, 2.0 * point.hdop); //only for dynamic channel adjustment + //} + } + // Turnaround (breakout) detection + if ((eleSmoothed <= (channelTop - channelThres)) && (climb == true)) { + if ((channelTop - channelBase) >= channelThres) { + diffElevationUp += channelTop - channelBase; + } + channelBase = channelTop; + channelBottom = eleSmoothed; + climb = false; + //channelThres = channelThresMin; //only for dynamic channel adjustment + } else if ((eleSmoothed >= (channelBottom + channelThres)) && (climb == false)) { + if ((channelBase - channelBottom) >= channelThres) { + diffElevationDown += channelBase - channelBottom; + } + channelBase = channelBottom; + channelTop = eleSmoothed; + climb = true; + //channelThres = channelThresMin; //only for dynamic channel adjustment + } + // End detection without breakout + if (j == (numberOfPoints - 1)) { + if ((channelTop - channelBase) >= channelThres) { + diffElevationUp += channelTop - channelBase; + } + if ((channelBase - channelBottom) >= channelThres) { + diffElevationDown += channelBase - channelBottom; + } + } + } + + if (j > 0) { + WptPt prev = s.get(j - 1); + + // Old complete summation approach for elevation gain/loss + //if (!Double.isNaN(point.ele) && !Double.isNaN(prev.ele)) { + // double diff = point.ele - prev.ele; + // if (diff > 0) { + // diffElevationUp += diff; + // } else { + // diffElevationDown -= diff; + // } + //} + + // totalDistance += MapUtils.getDistance(prev.lat, prev.lon, point.lat, point.lon); + // using ellipsoidal 'distanceBetween' instead of spherical haversine (MapUtils.getDistance) is + // a little more exact, also seems slightly faster: + net.osmand.Location.distanceBetween(prev.lat, prev.lon, point.lat, point.lon, calculations); + totalDistance += calculations[0]; + segmentDistance += calculations[0]; + point.distance = segmentDistance; + timeDiff = (int) ((point.time - prev.time) / 1000); + + //Last resort: Derive speed values from displacement if track does not originally contain speed + if (!hasSpeedInTrack && speed == 0 && timeDiff > 0) { + speed = calculations[0] / timeDiff; + } + + // Motion detection: + // speed > 0 uses GPS chipset's motion detection + // calculations[0] > minDisplacment * time is heuristic needed because tracks may be filtered at recording time, so points at rest may not be present in file at all + if ((speed > 0) && (calculations[0] > 0.1 / 1000f * (point.time - prev.time)) && point.time != 0 && prev.time != 0) { + timeMoving = timeMoving + (point.time - prev.time); + totalDistanceMoving += calculations[0]; + } + + //Next few lines for Issue 3222 heuristic testing only + // if (speed > 0 && point.time != 0 && prev.time != 0) { + // timeMoving0 = timeMoving0 + (point.time - prev.time); + // totalDistanceMoving0 += calculations[0]; + // } + } + + elevation1.time = timeDiff; + elevation1.distance = (j > 0) ? calculations[0] : 0; + elevationData.add(elevation1); + if (!hasElevationData && !Float.isNaN(elevation1.elevation) && totalDistance > 0) { + hasElevationData = true; + } + + minSpeed = Math.min(speed, minSpeed); + if (speed > 0) { + totalSpeedSum += speed; + maxSpeed = Math.max(speed, maxSpeed); + speedCount++; + } + + Speed speed1 = new Speed(); + speed1.speed = speed; + speed1.time = timeDiff; + speed1.distance = elevation1.distance; + speedData.add(speed1); + if (!hasSpeedData && speed1.speed > 0 && totalDistance > 0) { + hasSpeedData = true; + } + } + } + if (totalDistance < 0) { + hasElevationData = false; + hasSpeedData = false; + } + if (!isTimeSpecified()) { + startTime = filestamp; + endTime = filestamp; + } + + // OUTPUT: + // 1. Total distance, Start time, End time + // 2. Time span + if (timeSpan == 0) { + timeSpan = endTime - startTime; + } + + // 3. Time moving, if any + // 4. Elevation, eleUp, eleDown, if recorded + if (elevationPoints > 0) { + avgElevation = totalElevation / elevationPoints; + } + + + // 5. Max speed and Average speed, if any. Average speed is NOT overall (effective) speed, but only calculated for "moving" periods. + // Averaging speed values is less precise than totalDistanceMoving/timeMoving + if (speedCount > 0) { + if (timeMoving > 0) { + avgSpeed = (float) totalDistanceMoving / (float) timeMoving * 1000f; + } else { + avgSpeed = (float) totalSpeedSum / (float) speedCount; + } + } else { + avgSpeed = -1; + } + return this; + } + + } + + private static class SplitSegment { + TrkSegment segment; + double startCoeff = 0; + int startPointInd; + double endCoeff = 0; + int endPointInd; + double metricEnd; + double secondaryMetricEnd; + + public SplitSegment(TrkSegment s) { + startPointInd = 0; + startCoeff = 0; + endPointInd = s.points.size() - 2; + endCoeff = 1; + this.segment = s; + } + + public SplitSegment(TrkSegment s, int pointInd, double cf) { + this.segment = s; + this.startPointInd = pointInd; + this.startCoeff = cf; + } + + + public int getNumberOfPoints() { + return endPointInd - startPointInd + 2; + } + + public WptPt get(int j) { + final int ind = j + startPointInd; + if (j == 0) { + if (startCoeff == 0) { + return segment.points.get(ind); + } + return approx(segment.points.get(ind), segment.points.get(ind + 1), startCoeff); + } + if (j == getNumberOfPoints() - 1) { + if (endCoeff == 1) { + return segment.points.get(ind); + } + return approx(segment.points.get(ind - 1), segment.points.get(ind), endCoeff); + } + return segment.points.get(ind); + } + + + private WptPt approx(WptPt w1, WptPt w2, double cf) { + long time = value(w1.time, w2.time, 0, cf); + double speed = value(w1.speed, w2.speed, 0, cf); + double ele = value(w1.ele, w2.ele, 0, cf); + double hdop = value(w1.hdop, w2.hdop, 0, cf); + double lat = value(w1.lat, w2.lat, -360, cf); + double lon = value(w1.lon, w2.lon, -360, cf); + return new WptPt(lat, lon, time, ele, speed, hdop); + } + + private double value(double vl, double vl2, double none, double cf) { + if (vl == none || Double.isNaN(vl)) { + return vl2; + } else if (vl2 == none || Double.isNaN(vl2)) { + return vl; + } + return vl + cf * (vl2 - vl); + } + + private long value(long vl, long vl2, long none, double cf) { + if (vl == none) { + return vl2; + } else if (vl2 == none) { + return vl; + } + return vl + ((long) (cf * (vl2 - vl))); + } + + + public double setLastPoint(int pointInd, double endCf) { + endCoeff = endCf; + endPointInd = pointInd; + return endCoeff; + } + + } + + private static SplitMetric getDistanceMetric() { + return new SplitMetric() { + + private float[] calculations = new float[1]; + + @Override + public double metric(WptPt p1, WptPt p2) { + net.osmand.Location.distanceBetween(p1.lat, p1.lon, p2.lat, p2.lon, calculations); + return calculations[0]; + } + }; + } + + private static SplitMetric getTimeSplit() { + return new SplitMetric() { + + @Override + public double metric(WptPt p1, WptPt p2) { + if (p1.time != 0 && p2.time != 0) { + return (int) Math.abs((p2.time - p1.time) / 1000l); + } + return 0; + } + }; + } + + private abstract static class SplitMetric { + + public abstract double metric(WptPt p1, WptPt p2); + + } + + private static void splitSegment(SplitMetric metric, SplitMetric secondaryMetric, + double metricLimit, List splitSegments, + TrkSegment segment) { + double currentMetricEnd = metricLimit; + double secondaryMetricEnd = 0; + SplitSegment sp = new SplitSegment(segment, 0, 0); + double total = 0; + WptPt prev = null; + for (int k = 0; k < segment.points.size(); k++) { + WptPt point = segment.points.get(k); + if (k > 0) { + double currentSegment = metric.metric(prev, point); + secondaryMetricEnd += secondaryMetric.metric(prev, point); + while (total + currentSegment > currentMetricEnd) { + double p = currentMetricEnd - total; + double cf = (p / currentSegment); + sp.setLastPoint(k - 1, cf); + sp.metricEnd = currentMetricEnd; + sp.secondaryMetricEnd = secondaryMetricEnd; + splitSegments.add(sp); + + sp = new SplitSegment(segment, k - 1, cf); + currentMetricEnd += metricLimit; + prev = sp.get(0); + } + total += currentSegment; + } + prev = point; + } + if (segment.points.size() > 0 + && !(sp.endPointInd == segment.points.size() - 1 && sp.startCoeff == 1)) { + sp.metricEnd = total; + sp.secondaryMetricEnd = secondaryMetricEnd; + sp.setLastPoint(segment.points.size() - 2, 1); + splitSegments.add(sp); + } + } + + private static List convert(List splitSegments) { + List ls = new ArrayList<>(); + for (SplitSegment s : splitSegments) { + GPXTrackAnalysis a = new GPXTrackAnalysis(); + a.prepareInformation(0, s); + ls.add(a); + } + return ls; + } + + public static class GPXFile extends GPXExtensions { + public String author; + public Metadata metadata; + public List tracks = new ArrayList<>(); + private List points = new ArrayList<>(); + public List routes = new ArrayList<>(); + + public String warning = null; + public String path = ""; + public boolean showCurrentTrack; + public long modifiedTime = 0; + public int userId; + public long chatId; + + private Track generalTrack; + private TrkSegment generalSegment; + + public List getPoints() { + return Collections.unmodifiableList(points); + } + + public Map> getPointsByCategories() { + Map> res = new HashMap<>(); + for (WptPt pt : points) { + String category = pt.category == null ? "" : pt.category; + List list = res.get(category); + if (list != null) { + list.add(pt); + } else { + list = new ArrayList<>(); + list.add(pt); + res.put(category, list); + } + } + return res; + } + + public boolean isPointsEmpty() { + return points.isEmpty(); + } + + public int getPointsSize() { + return points.size(); + } + + boolean containsPoint(WptPt point) { + return points.contains(point); + } + + void clearPoints() { + points.clear(); + modifiedTime = System.currentTimeMillis(); + } + + public void addPoint(WptPt point) { + points.add(point); + modifiedTime = System.currentTimeMillis(); + } + + public void addPoint(int position, WptPt point) { + points.add(position, point); + modifiedTime = System.currentTimeMillis(); + } + + void addPoints(Collection collection) { + points.addAll(collection); + modifiedTime = System.currentTimeMillis(); + } + + public boolean isCloudmadeRouteFile() { + return "cloudmade".equalsIgnoreCase(author); + } + + public void addGeneralTrack() { + Track generalTrack = getGeneralTrack(); + if (generalTrack != null && !tracks.contains(generalTrack)) { + tracks.add(0, generalTrack); + } + } + + public Track getGeneralTrack() { + TrkSegment generalSegment = getGeneralSegment(); + if (generalTrack == null && generalSegment != null) { + Track track = new Track(); + track.segments = new ArrayList<>(); + track.segments.add(generalSegment); + generalTrack = track; + track.generalTrack = true; + } + return generalTrack; + } + + public TrkSegment getGeneralSegment() { + if (generalSegment == null && getNonEmptySegmentsCount() > 1) { + buildGeneralSegment(); + } + return generalSegment; + } + + private void buildGeneralSegment() { + TrkSegment segment = new TrkSegment(); + for (Track track : tracks) { + for (TrkSegment s : track.segments) { + if (s.points.size() > 0) { + List waypoints = new ArrayList<>(s.points.size()); + for (WptPt wptPt : s.points) { + waypoints.add(new WptPt(wptPt)); + } + waypoints.get(0).firstPoint = true; + waypoints.get(waypoints.size() - 1).lastPoint = true; + segment.points.addAll(waypoints); + } + } + } + if (segment.points.size() > 0) { + segment.generalSegment = true; + generalSegment = segment; + } + } + + public GPXTrackAnalysis getAnalysis(long fileTimestamp) { + GPXTrackAnalysis g = new GPXTrackAnalysis(); + g.wptPoints = points.size(); + g.wptCategoryNames = getWaypointCategories(true); + List splitSegments = new ArrayList(); + for (int i = 0; i < tracks.size(); i++) { + Track subtrack = tracks.get(i); + for (TrkSegment segment : subtrack.segments) { + if (!segment.generalSegment) { + g.totalTracks++; + if (segment.points.size() > 1) { + splitSegments.add(new SplitSegment(segment)); + } + } + } + } + g.prepareInformation(fileTimestamp, splitSegments.toArray(new SplitSegment[splitSegments.size()])); + return g; + } + + public List getRoutePoints() { + List points = new ArrayList<>(); + for (int i = 0; i < routes.size(); i++) { + Route rt = routes.get(i); + points.addAll(rt.points); + } + return points; + } + + public boolean hasRtePt() { + for (Route r : routes) { + if (r.points.size() > 0) { + return true; + } + } + return false; + } + + public boolean hasWptPt() { + return points.size() > 0; + } + + public boolean hasTrkPt() { + for (Track t : tracks) { + for (TrkSegment ts : t.segments) { + if (ts.points.size() > 0) { + return true; + } + } + } + return false; + } + + public WptPt addWptPt(double lat, double lon, long time, String description, String name, String category, int color) { + double latAdjusted = Double.parseDouble(latLonFormat.format(lat)); + double lonAdjusted = Double.parseDouble(latLonFormat.format(lon)); + final WptPt pt = new WptPt(latAdjusted, lonAdjusted, time, Double.NaN, 0, Double.NaN); + pt.name = name; + pt.category = category; + pt.desc = description; + if (color != 0) { + pt.setColor(color); + } + + points.add(pt); + + modifiedTime = System.currentTimeMillis(); + + return pt; + } + + public WptPt addRtePt(double lat, double lon, long time, String description, String name, String category, int color) { + double latAdjusted = Double.parseDouble(latLonFormat.format(lat)); + double lonAdjusted = Double.parseDouble(latLonFormat.format(lon)); + final WptPt pt = new WptPt(latAdjusted, lonAdjusted, time, Double.NaN, 0, Double.NaN); + pt.name = name; + pt.category = category; + pt.desc = description; + if (color != 0) { + pt.setColor(color); + } + + if (routes.size() == 0) { + routes.add(new Route()); + } + Route currentRoute = routes.get(routes.size() - 1); + currentRoute.points.add(pt); + + modifiedTime = System.currentTimeMillis(); + + return pt; + } + + public void addTrkSegment(List points) { + removeGeneralTrackIfExists(); + + TrkSegment segment = new TrkSegment(); + segment.points.addAll(points); + + if (tracks.size() == 0) { + tracks.add(new Track()); + } + Track lastTrack = tracks.get(tracks.size() - 1); + lastTrack.segments.add(segment); + + modifiedTime = System.currentTimeMillis(); + } + + public boolean replaceSegment(TrkSegment oldSegment, TrkSegment newSegment) { + removeGeneralTrackIfExists(); + + for (int i = 0; i < tracks.size(); i++) { + Track currentTrack = tracks.get(i); + for (int j = 0; j < currentTrack.segments.size(); j++) { + int segmentIndex = currentTrack.segments.indexOf(oldSegment); + if (segmentIndex != -1) { + currentTrack.segments.remove(segmentIndex); + currentTrack.segments.add(segmentIndex, newSegment); + addGeneralTrack(); + modifiedTime = System.currentTimeMillis(); + return true; + } + } + } + + addGeneralTrack(); + return false; + } + + public void addRoutePoints(List points) { + if (routes.size() == 0) { + Route route = new Route(); + routes.add(route); + } + + Route lastRoute = routes.get(routes.size() - 1); + lastRoute.points.addAll(points); + modifiedTime = System.currentTimeMillis(); + } + + public void replaceRoutePoints(List points) { + routes.clear(); + routes.add(new Route()); + Route currentRoute = routes.get(routes.size() - 1); + currentRoute.points.addAll(points); + modifiedTime = System.currentTimeMillis(); + } + + public void updateWptPt(WptPt pt, double lat, double lon, long time, String description, String name, String category, int color) { + int index = points.indexOf(pt); + double latAdjusted = Double.parseDouble(latLonFormat.format(lat)); + double lonAdjusted = Double.parseDouble(latLonFormat.format(lon)); + pt.lat = latAdjusted; + pt.lon = lonAdjusted; + pt.time = time; + pt.desc = description; + pt.name = name; + pt.category = category; + if (color != 0) { + pt.setColor(color); + } + + if (index != -1) { + points.set(index, pt); + } + modifiedTime = System.currentTimeMillis(); + } + + private void removeGeneralTrackIfExists() { + if (generalTrack != null) { + tracks.remove(generalTrack); + this.generalTrack = null; + this.generalSegment = null; + } + } + + public boolean removeTrkSegment(TrkSegment segment) { + removeGeneralTrackIfExists(); + + for (int i = 0; i < tracks.size(); i++) { + Track currentTrack = tracks.get(i); + for (int j = 0; j < currentTrack.segments.size(); j++) { + if (currentTrack.segments.remove(segment)) { + addGeneralTrack(); + modifiedTime = System.currentTimeMillis(); + return true; + } + } + } + addGeneralTrack(); + return false; + } + + public boolean deleteWptPt(WptPt pt) { + modifiedTime = System.currentTimeMillis(); + return points.remove(pt); + } + + public boolean deleteRtePt(WptPt pt) { + modifiedTime = System.currentTimeMillis(); + for (Route route : routes) { + if (route.points.remove(pt)) { + return true; + } + } + return false; + } + + public List processRoutePoints() { + List tpoints = new ArrayList(); + if (routes.size() > 0) { + for (Route r : routes) { + int routeColor = r.getColor(getColor(0)); + if (r.points.size() > 0) { + TrkSegment sgmt = new TrkSegment(); + tpoints.add(sgmt); + sgmt.points.addAll(r.points); + sgmt.setColor(routeColor); + } + } + } + return tpoints; + } + + public List proccessPoints() { + List tpoints = new ArrayList(); + for (Track t : tracks) { + int trackColor = t.getColor(getColor(0)); + for (TrkSegment ts : t.segments) { + if (!ts.generalSegment && ts.points.size() > 0) { + TrkSegment sgmt = new TrkSegment(); + tpoints.add(sgmt); + sgmt.points.addAll(ts.points); + sgmt.setColor(trackColor); + } + } + } + return tpoints; + } + + public WptPt getLastPoint() { + if (tracks.size() > 0) { + Track tk = tracks.get(tracks.size() - 1); + if (tk.segments.size() > 0) { + TrkSegment ts = tk.segments.get(tk.segments.size() - 1); + if (ts.points.size() > 0) { + return ts.points.get(ts.points.size() - 1); + } + } + } + return null; + } + + public WptPt findPointToShow() { + for (Track t : tracks) { + for (TrkSegment s : t.segments) { + if (s.points.size() > 0) { + return s.points.get(0); + } + } + } + for (Route s : routes) { + if (s.points.size() > 0) { + return s.points.get(0); + } + } + if (points.size() > 0) { + return points.get(0); + } + return null; + } + + public boolean isEmpty() { + for (Track t : tracks) { + if (t.segments != null) { + for (TrkSegment s : t.segments) { + boolean tracksEmpty = s.points.isEmpty(); + if (!tracksEmpty) { + return false; + } + } + } + } + return points.isEmpty() && routes.isEmpty(); + } + + public int getNonEmptySegmentsCount() { + int count = 0; + for (Track t : tracks) { + for (TrkSegment s : t.segments) { + if (s.points.size() > 0) { + count++; + } + } + } + return count; + } + + public Set getWaypointCategories(boolean withDefaultCategory) { + Set categories = new HashSet<>(); + for (WptPt pt : points) { + String category = pt.category == null ? "" : pt.category; + if (withDefaultCategory || !TextUtils.isEmpty(category)) { + categories.add(category); + } + } + return categories; + } + + public Map getWaypointCategoriesWithColors(boolean withDefaultCategory) { + Map categories = new HashMap<>(); + for (WptPt pt : points) { + String category = pt.category == null ? "" : pt.category; + int color = pt.category == null ? 0 : pt.getColor(); + boolean emptyCategory = TextUtils.isEmpty(category); + if (!emptyCategory) { + Integer existingColor = categories.get(category); + if (existingColor == null || (existingColor == 0 && color != 0)) { + categories.put(category, color); + } + } else if (withDefaultCategory) { + categories.put(category, 0); + } + } + return categories; + } + + public QuadRect getRect() { + double left = 0, right = 0; + double top = 0, bottom = 0; + for (Track track : tracks) { + for (TrkSegment segment : track.segments) { + for (WptPt p : segment.points) { + if (left == 0 && right == 0) { + left = p.getLongitude(); + right = p.getLongitude(); + top = p.getLatitude(); + bottom = p.getLatitude(); + } else { + left = Math.min(left, p.getLongitude()); + right = Math.max(right, p.getLongitude()); + top = Math.max(top, p.getLatitude()); + bottom = Math.min(bottom, p.getLatitude()); + } + } + } + } + for (WptPt p : points) { + if (left == 0 && right == 0) { + left = p.getLongitude(); + right = p.getLongitude(); + top = p.getLatitude(); + bottom = p.getLatitude(); + } else { + left = Math.min(left, p.getLongitude()); + right = Math.max(right, p.getLongitude()); + top = Math.max(top, p.getLatitude()); + bottom = Math.min(bottom, p.getLatitude()); + } + } + for (GPXUtilities.Route route : routes) { + for (WptPt p : route.points) { + if (left == 0 && right == 0) { + left = p.getLongitude(); + right = p.getLongitude(); + top = p.getLatitude(); + bottom = p.getLatitude(); + } else { + left = Math.min(left, p.getLongitude()); + right = Math.max(right, p.getLongitude()); + top = Math.max(top, p.getLatitude()); + bottom = Math.min(bottom, p.getLatitude()); + } + } + } + return new QuadRect(left, top, right, bottom); + } + } + + public static String asString(GPXFile file, TelegramApplication ctx) { + final Writer writer = new StringWriter(); + GPXUtilities.writeGpx(writer, file, ctx); + return writer.toString(); + } + + public static String writeGpxFile(File fout, GPXFile file, TelegramApplication ctx) { + Writer output = null; + try { + if (fout.getParentFile() != null) { + fout.getParentFile().mkdirs(); + } + output = new OutputStreamWriter(new FileOutputStream(fout), "UTF-8"); //$NON-NLS-1$ + if (Algorithms.isEmpty(file.path)) { + file.path = fout.getAbsolutePath(); + } + String msg = writeGpx(output, file, ctx); + return msg; + } catch (IOException e) { + log.error("Error saving gpx", e); //$NON-NLS-1$ + return "Error saving gpx"; + } finally { + if (output != null) { + try { + output.close(); + } catch (IOException ignore) { + // ignore + } + } + } + } + + public static String writeGpx(Writer output, GPXFile file, TelegramApplication ctx) { + try { + SimpleDateFormat format = new SimpleDateFormat(GPX_TIME_FORMAT, Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + XmlSerializer serializer = PlatformUtil.newSerializer(); + serializer.setOutput(output); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); //$NON-NLS-1$ + serializer.startDocument("UTF-8", true); //$NON-NLS-1$ + serializer.startTag(null, "gpx"); //$NON-NLS-1$ + serializer.attribute(null, "version", "1.1"); //$NON-NLS-1$ //$NON-NLS-2$ + if (file.author == null) { + serializer.attribute(null, "creator", ctx.getPackageName()); //$NON-NLS-1$ + } else { + serializer.attribute(null, "creator", file.author); //$NON-NLS-1$ + } + serializer.attribute(null, "xmlns", "http://www.topografix.com/GPX/1/1"); //$NON-NLS-1$ //$NON-NLS-2$ + serializer.attribute(null, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); + serializer.attribute(null, "xsi:schemaLocation", + "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"); + + String trackName = getFilename(file.path); + serializer.startTag(null, "metadata"); + writeNotNullText(serializer, "name", trackName); + if (file.metadata != null) { + writeNotNullText(serializer, "desc", file.metadata.desc); + writeExtensions(serializer, file.metadata); + } + serializer.endTag(null, "metadata"); + + + for (Track track : file.tracks) { + if (!track.generalTrack) { + serializer.startTag(null, "trk"); //$NON-NLS-1$ + writeNotNullText(serializer, "name", track.name); + writeNotNullText(serializer, "desc", track.desc); + for (TrkSegment segment : track.segments) { + serializer.startTag(null, "trkseg"); //$NON-NLS-1$ + for (WptPt p : segment.points) { + serializer.startTag(null, "trkpt"); //$NON-NLS-1$ + writeWpt(format, serializer, p); + serializer.endTag(null, "trkpt"); //$NON-NLS-1$ + } + serializer.endTag(null, "trkseg"); //$NON-NLS-1$ + } + writeExtensions(serializer, track); + serializer.endTag(null, "trk"); //$NON-NLS-1$ + } + } + + for (Route track : file.routes) { + serializer.startTag(null, "rte"); //$NON-NLS-1$ + writeNotNullText(serializer, "name", track.name); + writeNotNullText(serializer, "desc", track.desc); + + for (WptPt p : track.points) { + serializer.startTag(null, "rtept"); //$NON-NLS-1$ + writeWpt(format, serializer, p); + serializer.endTag(null, "rtept"); //$NON-NLS-1$ + } + writeExtensions(serializer, track); + serializer.endTag(null, "rte"); //$NON-NLS-1$ + } + + for (WptPt l : file.points) { + serializer.startTag(null, "wpt"); //$NON-NLS-1$ + writeWpt(format, serializer, l); + serializer.endTag(null, "wpt"); //$NON-NLS-1$ + } + + serializer.endTag(null, "gpx"); //$NON-NLS-1$ + serializer.endDocument(); + serializer.flush(); + + + } catch (RuntimeException e) { + log.error("Error saving gpx", e); //$NON-NLS-1$ + return "Error saving gpx"; + } catch (IOException e) { + log.error("Error saving gpx", e); //$NON-NLS-1$ + return "Error saving gpx"; + } + return null; + } + + private static String getFilename(String path) { + if (path != null) { + int i = path.lastIndexOf('/'); + if (i > 0) { + path = path.substring(i + 1); + } + i = path.lastIndexOf('.'); + if (i > 0) { + path = path.substring(0, i); + } + } + return path; + } + + private static void writeNotNullText(XmlSerializer serializer, String tag, String value) throws IOException { + if (value != null) { + serializer.startTag(null, tag); + serializer.text(value); + serializer.endTag(null, tag); + } + } + + private static void writeExtensions(XmlSerializer serializer, GPXExtensions p) throws IOException { + if (!p.getExtensionsToRead().isEmpty()) { + serializer.startTag(null, "extensions"); + for (Map.Entry s : p.getExtensionsToRead().entrySet()) { + writeNotNullText(serializer, s.getKey(), s.getValue()); + } + serializer.endTag(null, "extensions"); + } + } + + private static void writeWpt(SimpleDateFormat format, XmlSerializer serializer, WptPt p) throws IOException { + serializer.attribute(null, "lat", latLonFormat.format(p.lat)); //$NON-NLS-1$ //$NON-NLS-2$ + serializer.attribute(null, "lon", latLonFormat.format(p.lon)); //$NON-NLS-1$ //$NON-NLS-2$ + + if (!Double.isNaN(p.ele)) { + writeNotNullText(serializer, "ele", decimalFormat.format(p.ele)); + } + if (p.time != 0) { + writeNotNullText(serializer, "time", format.format(new Date(p.time))); + } + writeNotNullText(serializer, "name", p.name); + writeNotNullText(serializer, "desc", p.desc); + if (p.link != null) { + serializer.startTag(null, "link"); + serializer.attribute(null, "href", p.link); + serializer.endTag(null, "link"); + } + writeNotNullText(serializer, "type", p.category); + if (p.comment != null) { + writeNotNullText(serializer, "cmt", p.comment); + } + if (!Double.isNaN(p.hdop)) { + writeNotNullText(serializer, "hdop", decimalFormat.format(p.hdop)); + } + if (p.speed > 0) { + p.getExtensionsToWrite().put("speed", decimalFormat.format(p.speed)); + } + writeExtensions(serializer, p); + } + + public static class GPXFileResult { + public ArrayList> locations = new ArrayList>(); + public ArrayList wayPoints = new ArrayList<>(); + // special case for cloudmate gpx : they discourage common schema + // by using waypoint as track points and rtept are not very close to real way + // such as wpt. However they provide additional information into gpx. + public boolean cloudMadeFile; + public String error; + + public Location findFistLocation() { + for (List l : locations) { + for (Location ls : l) { + if (ls != null) { + return ls; + } + } + } + return null; + } + } + + private static String readText(XmlPullParser parser, String key) throws XmlPullParserException, IOException { + int tok; + String text = null; + while ((tok = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (tok == XmlPullParser.END_TAG && parser.getName().equals(key)) { + break; + } else if (tok == XmlPullParser.TEXT) { + if (text == null) { + text = parser.getText(); + } else { + text += parser.getText(); + } + } + + } + return text; + } + + public static GPXFile loadGPXFile(Context ctx, File f) { + FileInputStream fis = null; + try { + fis = new FileInputStream(f); + GPXFile file = loadGPXFile(ctx, fis); + file.path = f.getAbsolutePath(); + try { + fis.close(); + } catch (IOException e) { + } + return file; + } catch (IOException e) { + GPXFile res = new GPXFile(); + res.path = f.getAbsolutePath(); + log.error("Error reading gpx " + res.path, e); //$NON-NLS-1$ + res.warning = "Error reading gpx "; + return res; + } finally { + try { + if (fis != null) + fis.close(); + } catch (IOException ignore) { + // ignore + } + } + } + + public static GPXFile loadGPXFile(Context ctx, InputStream f) { + GPXFile res = new GPXFile(); + SimpleDateFormat format = new SimpleDateFormat(GPX_TIME_FORMAT, Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + SimpleDateFormat formatMillis = new SimpleDateFormat(GPX_TIME_FORMAT_MILLIS, Locale.US); + formatMillis.setTimeZone(TimeZone.getTimeZone("UTC")); + try { + XmlPullParser parser = PlatformUtil.newXMLPullParser(); + parser.setInput(getUTF8Reader(f)); + Stack parserState = new Stack<>(); + boolean extensionReadMode = false; + parserState.push(res); + int tok; + while ((tok = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (tok == XmlPullParser.START_TAG) { + Object parse = parserState.peek(); + String tag = parser.getName(); + if (extensionReadMode && parse instanceof GPXExtensions) { + String value = readText(parser, tag); + if (value != null) { + ((GPXExtensions) parse).getExtensionsToWrite().put(tag.toLowerCase(), value); + if (tag.equals("speed") && parse instanceof WptPt) { + try { + ((WptPt) parse).speed = Float.parseFloat(value); + } catch (NumberFormatException e) { + } + } + } + + } else if (parse instanceof GPXExtensions && tag.equals("extensions")) { + extensionReadMode = true; + } else { + if (parse instanceof GPXFile) { + if (tag.equals("gpx")) { + ((GPXFile) parse).author = parser.getAttributeValue("", "creator"); + } + if (tag.equals("metadata")) { + Metadata metadata = new Metadata(); + ((GPXFile) parse).metadata = metadata; + parserState.push(metadata); + } + if (tag.equals("trk")) { + Track track = new Track(); + ((GPXFile) parse).tracks.add(track); + parserState.push(track); + } + if (tag.equals("rte")) { + Route route = new Route(); + ((GPXFile) parse).routes.add(route); + parserState.push(route); + } + if (tag.equals("wpt")) { + WptPt wptPt = parseWptAttributes(parser); + ((GPXFile) parse).points.add(wptPt); + parserState.push(wptPt); + } + } else if (parse instanceof Metadata) { + if (tag.equals("desc")) { + ((Metadata) parse).desc = readText(parser, "desc"); + } + } else if (parse instanceof Route) { + if (tag.equals("name")) { + ((Route) parse).name = readText(parser, "name"); + } + if (tag.equals("desc")) { + ((Route) parse).desc = readText(parser, "desc"); + } + if (tag.equals("rtept")) { + WptPt wptPt = parseWptAttributes(parser); + ((Route) parse).points.add(wptPt); + parserState.push(wptPt); + } + } else if (parse instanceof Track) { + if (tag.equals("name")) { + ((Track) parse).name = readText(parser, "name"); + } + if (tag.equals("desc")) { + ((Track) parse).desc = readText(parser, "desc"); + } + if (tag.equals("trkseg")) { + TrkSegment trkSeg = new TrkSegment(); + ((Track) parse).segments.add(trkSeg); + parserState.push(trkSeg); + } + } else if (parse instanceof TrkSegment) { + if (tag.equals("trkpt")) { + WptPt wptPt = parseWptAttributes(parser); + ((TrkSegment) parse).points.add(wptPt); + parserState.push(wptPt); + } + if (tag.equals("csvattributes")) { + String segmentPoints = readText(parser, "csvattributes"); + String[] pointsArr = segmentPoints.split("\n"); + for (int i = 0; i < pointsArr.length; i++) { + String[] pointAttrs = pointsArr[i].split(","); + try { + int arrLength = pointsArr.length; + if (arrLength > 1) { + WptPt wptPt = new WptPt(); + wptPt.lon = Double.parseDouble(pointAttrs[0]); + wptPt.lat = Double.parseDouble(pointAttrs[1]); + ((TrkSegment) parse).points.add(wptPt); + if (arrLength > 2) { + wptPt.ele = Double.parseDouble(pointAttrs[2]); + } + } + } catch (NumberFormatException e) { + } + } + } + // main object to parse + } else if (parse instanceof WptPt) { + if (tag.equals("name")) { + ((WptPt) parse).name = readText(parser, "name"); + } else if (tag.equals("desc")) { + ((WptPt) parse).desc = readText(parser, "desc"); + } else if (tag.equals("cmt")) { + ((WptPt) parse).comment = readText(parser, "cmt"); + } else if (tag.equals("speed")) { + try { + String value = readText(parser, "speed"); + ((WptPt) parse).speed = Float.parseFloat(value); + ((WptPt) parse).getExtensionsToWrite().put("speed", value); + } catch (NumberFormatException e) { + } + } else if (tag.equals("link")) { + ((WptPt) parse).link = parser.getAttributeValue("", "href"); + } else if (tag.equals("category")) { + ((WptPt) parse).category = readText(parser, "category"); + } else if (tag.equals("type")) { + if (((WptPt) parse).category == null) { + ((WptPt) parse).category = readText(parser, "type"); + } + } else if (tag.equals("ele")) { + String text = readText(parser, "ele"); + if (text != null) { + try { + ((WptPt) parse).ele = Float.parseFloat(text); + } catch (NumberFormatException e) { + } + } + } else if (tag.equals("hdop")) { + String text = readText(parser, "hdop"); + if (text != null) { + try { + ((WptPt) parse).hdop = Float.parseFloat(text); + } catch (NumberFormatException e) { + } + } + } else if (tag.equals("time")) { + String text = readText(parser, "time"); + if (text != null) { + try { + ((WptPt) parse).time = format.parse(text).getTime(); + } catch (ParseException e1) { + try { + ((WptPt) parse).time = formatMillis.parse(text).getTime(); + } catch (ParseException e2) { + + } + } + } + } + } + } + + } else if (tok == XmlPullParser.END_TAG) { + Object parse = parserState.peek(); + String tag = parser.getName(); + if (parse instanceof GPXExtensions && tag.equals("extensions")) { + extensionReadMode = false; + } + + if (tag.equals("metadata")) { + Object pop = parserState.pop(); + assert pop instanceof Metadata; + } else if (tag.equals("trkpt")) { + Object pop = parserState.pop(); + assert pop instanceof WptPt; + } else if (tag.equals("wpt")) { + Object pop = parserState.pop(); + assert pop instanceof WptPt; + } else if (tag.equals("rtept")) { + Object pop = parserState.pop(); + assert pop instanceof WptPt; + } else if (tag.equals("trk")) { + Object pop = parserState.pop(); + assert pop instanceof Track; + } else if (tag.equals("rte")) { + Object pop = parserState.pop(); + assert pop instanceof Route; + } else if (tag.equals("trkseg")) { + Object pop = parserState.pop(); + assert pop instanceof TrkSegment; + } + } + } + } catch (RuntimeException e) { + log.error("Error reading gpx", e); //$NON-NLS-1$ + res.warning = "Error reading gpx" + " " + e.getMessage(); + } catch (XmlPullParserException e) { + log.error("Error reading gpx", e); //$NON-NLS-1$ + res.warning = "Error reading gpx" + " " + e.getMessage(); + } catch (IOException e) { + log.error("Error reading gpx", e); //$NON-NLS-1$ + res.warning = "Error reading gpx" + " " + e.getMessage(); + } + + return res; + } + + private static Reader getUTF8Reader(InputStream f) throws IOException { + BufferedInputStream bis = new BufferedInputStream(f); + assert bis.markSupported(); + bis.mark(3); + boolean reset = true; + byte[] t = new byte[3]; + bis.read(t); + if (t[0] == ((byte) 0xef) && t[1] == ((byte) 0xbb) && t[2] == ((byte) 0xbf)) { + reset = false; + } + if (reset) { + bis.reset(); + } + return new InputStreamReader(bis, "UTF-8"); + } + + private static WptPt parseWptAttributes(XmlPullParser parser) { + WptPt wpt = new WptPt(); + try { + wpt.lat = Double.parseDouble(parser.getAttributeValue("", "lat")); //$NON-NLS-1$ //$NON-NLS-2$ + wpt.lon = Double.parseDouble(parser.getAttributeValue("", "lon")); //$NON-NLS-1$ //$NON-NLS-2$ + } catch (NumberFormatException e) { + } + return wpt; + } + + public static void mergeGPXFileInto(GPXFile to, GPXFile from) { + if (from == null) { + return; + } + if (from.showCurrentTrack) { + to.showCurrentTrack = true; + } + if (from.points != null) { + to.points.addAll(from.points); + } + if (from.tracks != null) { + to.tracks.addAll(from.tracks); + } + if (from.routes != null) { + to.routes.addAll(from.routes); + } + if (from.warning != null) { + to.warning = from.warning; + } + } +} diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/LocationPoint.java b/OsmAnd-telegram/src/net/osmand/telegram/utils/LocationPoint.java new file mode 100644 index 0000000000..220896e389 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/LocationPoint.java @@ -0,0 +1,24 @@ +package net.osmand.telegram.utils; + + +import android.content.Context; + +/** + */ +public interface LocationPoint { + + public double getLatitude(); + + public double getLongitude(); + + public int getColor(); + + public boolean isVisible(); + +// public PointDescription getPointDescription(Context ctx); + +// public String getSpeakableName(); + + //public void prepareCommandPlayer(CommandBuilder cmd, String names); + +} \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandApiUtils.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandApiUtils.kt index be10f8f601..fe58650860 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandApiUtils.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandApiUtils.kt @@ -1,46 +1,97 @@ package net.osmand.telegram.utils import net.osmand.PlatformUtil +import net.osmand.telegram.SHARE_DEVICES_KEY import net.osmand.telegram.TelegramApplication import net.osmand.telegram.TelegramSettings +import org.drinkless.td.libcore.telegram.TdApi import org.json.JSONException import org.json.JSONObject +const val BASE_URL = "https://live.osmand.net" + +const val BASE_SHARING_URL = "http://osmand.net/go" + object OsmandApiUtils { private val log = PlatformUtil.getLog(OsmandApiUtils::class.java) fun updateSharingDevices(app: TelegramApplication, userId: Int) { - AndroidNetworkUtils.sendRequestAsync( - "https://osmand.net/device/send-devices?uid=$userId", + AndroidNetworkUtils.sendRequestAsync(app, "$BASE_URL/device/send-devices?uid=$userId", null, "Get Devices", true, false, object : AndroidNetworkUtils.OnRequestResultListener { - override fun onResult(result: String) { - val list = parseJsonContents(result) - app.settings.updateShareDevicesIds(list) + override fun onResult(result: String?) { + if (result != null) { + val list = parseJsonContents(result) + app.settings.updateShareDevices(list) + } } } ) } + fun createNewDevice( + app: TelegramApplication, + user: TdApi.User, + isBot: Boolean, + deviceName: String, + chatId: Long, + listener: AndroidNetworkUtils.OnRequestResultListener + ) { + val json = getNewDeviceJson(user, isBot, deviceName, chatId) + if (json != null) { + AndroidNetworkUtils.sendRequestAsync(app, "$BASE_URL/device/new", json.toString(), "add Device", true, true, listener) + } + } + + fun parseDeviceBot(deviceJSON: JSONObject): TelegramSettings.DeviceBot? { + return try { + TelegramSettings.DeviceBot().apply { + id = deviceJSON.optLong(TelegramSettings.DeviceBot.DEVICE_ID) + userId = deviceJSON.optLong(TelegramSettings.DeviceBot.USER_ID) + chatId = deviceJSON.optLong(TelegramSettings.DeviceBot.CHAT_ID) + deviceName = deviceJSON.optString(TelegramSettings.DeviceBot.DEVICE_NAME) + externalId = deviceJSON.optString(TelegramSettings.DeviceBot.EXTERNAL_ID) + data = deviceJSON.optString(TelegramSettings.DeviceBot.DATA) + } + } catch (e: JSONException) { + log.error(e.message, e) + null + } + } + fun parseJsonContents(contentsJson: String): List { val list = mutableListOf() try { - val jArray = JSONObject(contentsJson).getJSONArray("devices") + val jArray = JSONObject(contentsJson).getJSONArray(SHARE_DEVICES_KEY) for (i in 0 until jArray.length()) { val deviceJSON = jArray.getJSONObject(i) - val deviceBot = TelegramSettings.DeviceBot().apply { - id = deviceJSON.getLong("id") - userId = deviceJSON.getLong("userId") - chatId = deviceJSON.getLong("chatId") - deviceName = deviceJSON.getString("deviceName") - externalId = deviceJSON.getString("externalId") - data = deviceJSON.getString("data") + val deviceBot = parseDeviceBot(deviceJSON) + if (deviceBot != null) { + list.add(deviceBot) } - list.add(deviceBot) } } catch (e: JSONException) { log.error(e.message, e) } return list } + + private fun getNewDeviceJson(user: TdApi.User, isBot: Boolean, deviceName: String, chatId: Long): JSONObject? { + return try { + val json = JSONObject() + json.put("deviceName", deviceName) + json.put("chatId", chatId) + val jsonUser = JSONObject() + jsonUser.put("id", user.id) + jsonUser.put("firstName", user.firstName) + jsonUser.put("isBot", isBot) + jsonUser.put("lastName", user.lastName) + jsonUser.put("userName", user.username) + jsonUser.put("languageCode", user.languageCode) + json.put("user", jsonUser) + } catch (e: JSONException) { + log.error(e) + null + } + } } \ No newline at end of file diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.kt index 7e8e4d9b32..3a9719848a 100644 --- a/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.kt +++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandFormatter.kt @@ -23,6 +23,7 @@ object OsmandFormatter { private const val SHORT_TIME_FORMAT = "%02d:%02d" private const val SIMPLE_TIME_OF_DAY_FORMAT = "HH:mm" private const val SIMPLE_DATE_FORMAT = "dd MMM HH:mm:ss" + private const val SHORT_DATE_FORMAT = "dd MMM yyyy" private const val MIN_DURATION_FOR_DATE_FORMAT = 48 * 60 * 60 @@ -77,8 +78,13 @@ object OsmandFormatter { } } - fun getFormattedDate(seconds: Long): String = - SimpleDateFormat(SIMPLE_DATE_FORMAT, Locale.getDefault()).format(seconds * 1000L) + fun getFormattedDate(seconds: Long, shortFormat: Boolean = false): String { + return if (shortFormat) { + SimpleDateFormat(SIMPLE_DATE_FORMAT, Locale.getDefault()).format(seconds * 1000L) + } else { + SimpleDateFormat(SHORT_DATE_FORMAT, Locale.getDefault()).format(seconds * 1000L) + } + } fun getListItemLiveTimeDescr(ctx: TelegramApplication, lastUpdated: Int, prefix: String = ""): String { return if (lastUpdated > 0) { diff --git a/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandLocationUtils.kt b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandLocationUtils.kt new file mode 100644 index 0000000000..64eed09fc0 --- /dev/null +++ b/OsmAnd-telegram/src/net/osmand/telegram/utils/OsmandLocationUtils.kt @@ -0,0 +1,559 @@ +package net.osmand.telegram.utils + +import android.os.AsyncTask +import net.osmand.Location +import net.osmand.data.LatLon +import net.osmand.telegram.TelegramApplication +import net.osmand.telegram.helpers.LocationMessages +import net.osmand.telegram.helpers.LocationMessages.BufferMessage +import net.osmand.telegram.helpers.LocationMessages.LocationMessage +import net.osmand.telegram.helpers.TelegramHelper +import net.osmand.telegram.helpers.TelegramUiHelper +import net.osmand.util.GeoPointParserUtil +import net.osmand.util.MapUtils +import org.drinkless.td.libcore.telegram.TdApi +import java.io.File +import java.text.SimpleDateFormat +import java.util.* + +const val TRACKS_DIR = "tracker/" + +object OsmandLocationUtils { + + const val DEVICE_PREFIX = "Device: " + const val LOCATION_PREFIX = "Location: " + const val LAST_LOCATION_PREFIX = "Last location: " + const val UPDATED_PREFIX = "Updated: " + const val USER_TEXT_LOCATION_TITLE = "\uD83D\uDDFA OsmAnd sharing:" + + const val SHARING_LINK = "https://play.google.com/store/apps/details?id=net.osmand.telegram" + + const val ALTITUDE_PREFIX = "Altitude: " + const val SPEED_PREFIX = "Speed: " + const val HDOP_PREFIX = "Horizontal precision: " + + const val NOW = "now" + const val FEW_SECONDS_AGO = "few seconds ago" + const val SECONDS_AGO_SUFFIX = " seconds ago" + const val MINUTES_AGO_SUFFIX = " minutes ago" + const val HOURS_AGO_SUFFIX = " hours ago" + const val UTC_FORMAT_SUFFIX = " UTC" + + val UTC_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US).apply { + timeZone = TimeZone.getTimeZone("UTC") + } + + val UTC_TIME_FORMAT = SimpleDateFormat("HH:mm:ss", Locale.US).apply { + timeZone = TimeZone.getTimeZone("UTC") + } + + fun getLastUpdatedTime(message: TdApi.Message): Int { + val content = message.content + return when (content) { + is MessageOsmAndBotLocation -> content.lastUpdated + is MessageUserLocation -> content.lastUpdated + else -> Math.max(message.editDate, message.date) + } + } + + fun getOsmAndBotDeviceName(message: TdApi.Message): String { + var deviceName = "" + if (message.replyMarkup is TdApi.ReplyMarkupInlineKeyboard) { + val replyMarkup = message.replyMarkup as TdApi.ReplyMarkupInlineKeyboard + try { + deviceName = replyMarkup.rows[0][1].text.split("\\s".toRegex())[1] + } catch (e: Exception) { + + } + } + return deviceName + } + + fun parseOsmAndBotLocation(message: TdApi.Message): MessageOsmAndBotLocation { + val messageLocation = message.content as TdApi.MessageLocation + return MessageOsmAndBotLocation().apply { + name = getOsmAndBotDeviceName(message) + lat = messageLocation.location.latitude + lon = messageLocation.location.longitude + lastUpdated = getLastUpdatedTime(message) + } + } + + fun parseUserMapLocation(message: TdApi.Message): MessageUserLocation { + val messageLocation = message.content as TdApi.MessageLocation + return MessageUserLocation().apply { + lat = messageLocation.location.latitude + lon = messageLocation.location.longitude + lastUpdated = getLastUpdatedTime(message) + type = LocationMessages.TYPE_USER_MAP + } + } + + fun parseMessage(message: TdApi.Message, helper: TelegramHelper, previousMessageLatLon: LatLon?): LocationMessage? { + var locationMessage: LocationMessage? = null + val oldContent = message.content + val messageType = getMessageType(message,helper) + var parsedMessageContent: MessageLocation? = null + if (messageType != -1) { + parsedMessageContent = when (messageType) { + LocationMessages.TYPE_BOT_TEXT -> { + parseTextLocation((oldContent as TdApi.MessageText).text) + } + LocationMessages.TYPE_USER_TEXT -> { + if (oldContent is TdApi.MessageText) { + parseTextLocation(oldContent.text, false) + } else { + oldContent as MessageUserLocation + } + } + LocationMessages.TYPE_BOT_MAP -> { + parseOsmAndBotLocation(message) + } + LocationMessages.TYPE_USER_MAP -> { + parseUserMapLocation(message) + } + else -> null + } + } + + if (parsedMessageContent != null) { + val distanceFromPrev = if (previousMessageLatLon != null) { + MapUtils.getDistance(previousMessageLatLon, parsedMessageContent.lat, parsedMessageContent.lon) } else 0.0 + + locationMessage = LocationMessage(helper.getSenderMessageId(message), message.chatId, parsedMessageContent.lat, + parsedMessageContent.lon, parsedMessageContent.altitude, parsedMessageContent.speed, parsedMessageContent.hdop, + parsedMessageContent.bearing, parsedMessageContent.lastUpdated * 1000L, messageType, message.id, distanceFromPrev) + } + + return locationMessage + } + + fun getMessageType(message: TdApi.Message, helper: TelegramHelper): Int { + val fromBot = helper.isOsmAndBot(message.senderUserId) + val viaBot = helper.isOsmAndBot(message.viaBotUserId) + val oldContent = message.content + return if (oldContent is TdApi.MessageText) { + if (fromBot || viaBot) { + LocationMessages.TYPE_BOT_TEXT + } else { + LocationMessages.TYPE_USER_TEXT + } + } else if (oldContent is TdApi.MessageLocation) { + if (fromBot || viaBot) { + LocationMessages.TYPE_BOT_MAP + } else { + LocationMessages.TYPE_USER_MAP + } + } else if (oldContent is MessageLocation) { + oldContent.type + } else { + -1 + } + } + + fun formatLocation(sig: Location): String { + return String.format(Locale.US, "%.5f, %.5f", sig.latitude, sig.longitude) + } + + fun formatLocation(sig: LocationMessage): String { + return String.format(Locale.US, "%.5f, %.5f", sig.lat, sig.lon) + } + + fun formatLocation(sig: BufferMessage): String { + return String.format(Locale.US, "%.5f, %.5f", sig.lat, sig.lon) + } + + fun formatFullTime(ti: Long): String { + val dt = Date(ti) + return UTC_DATE_FORMAT.format(dt) + " " + UTC_TIME_FORMAT.format(dt) + " UTC" + } + + fun parseOsmAndBotLocationContent(oldContent: MessageOsmAndBotLocation, content: TdApi.MessageContent): MessageOsmAndBotLocation { + val messageLocation = content as TdApi.MessageLocation + return MessageOsmAndBotLocation().apply { + name = oldContent.name + lat = messageLocation.location.latitude + lon = messageLocation.location.longitude + lastUpdated = (System.currentTimeMillis() / 1000).toInt() + type = LocationMessages.TYPE_BOT_MAP + } + } + + fun parseTextLocation(text: TdApi.FormattedText, botLocation: Boolean = true): MessageLocation { + val res = if (botLocation) MessageOsmAndBotLocation() else MessageUserLocation() + res.type = if (botLocation) LocationMessages.TYPE_BOT_TEXT else LocationMessages.TYPE_USER_TEXT + var locationNA = false + for (s in text.text.lines()) { + when { + s.startsWith(DEVICE_PREFIX) -> { + if (res is MessageOsmAndBotLocation) { + res.name = s.removePrefix(DEVICE_PREFIX) + } + } + s.startsWith(LOCATION_PREFIX) || s.startsWith(LAST_LOCATION_PREFIX) -> { + var locStr: String + var parse = true + if (s.startsWith(LAST_LOCATION_PREFIX)) { + locStr = s.removePrefix(LAST_LOCATION_PREFIX) + if (!locationNA) { + parse = false + } + } else { + locStr = s.removePrefix(LOCATION_PREFIX) + if (locStr.trim() == "n/a") { + locationNA = true + parse = false + } + } + if (parse) { + try { + val urlTextEntity = + text.entities.firstOrNull { it.type is TdApi.TextEntityTypeTextUrl } + if (urlTextEntity != null && urlTextEntity.offset == text.text.indexOf( + locStr + ) + ) { + val url = (urlTextEntity.type as TdApi.TextEntityTypeTextUrl).url + val point: GeoPointParserUtil.GeoParsedPoint? = + GeoPointParserUtil.parse(url) + if (point != null) { + res.lat = point.latitude + res.lon = point.longitude + } + } else { + val (latS, lonS) = locStr.split(" ") + res.lat = latS.dropLast(1).toDouble() + res.lon = lonS.toDouble() + + val timeIndex = locStr.indexOf("(") + if (timeIndex != -1) { + val updatedS = locStr.substring(timeIndex, locStr.length) + res.lastUpdated = + (parseTime(updatedS.removePrefix("(").removeSuffix(")")) / 1000).toInt() + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + } + s.startsWith(ALTITUDE_PREFIX) -> { + val altStr = s.removePrefix(ALTITUDE_PREFIX) + try { + val alt = altStr.split(" ").first() + res.altitude = alt.toDouble() + } catch (e: Exception) { + e.printStackTrace() + } + } + s.startsWith(SPEED_PREFIX) -> { + val altStr = s.removePrefix(SPEED_PREFIX) + try { + val alt = altStr.split(" ").first() + res.speed = alt.toDouble() + } catch (e: Exception) { + e.printStackTrace() + } + } + s.startsWith(HDOP_PREFIX) -> { + val altStr = s.removePrefix(HDOP_PREFIX) + try { + val alt = altStr.split(" ").first() + res.hdop = alt.toDouble() + } catch (e: Exception) { + e.printStackTrace() + } + } + s.startsWith(UPDATED_PREFIX) -> { + if (res.lastUpdated == 0) { + val updatedStr = s.removePrefix(UPDATED_PREFIX) + val endIndex = updatedStr.indexOf("(") + val updatedS = updatedStr.substring( + 0, + if (endIndex != -1) endIndex else updatedStr.length + ) + val parsedTime = (parseTime(updatedS.trim()) / 1000).toInt() + val currentTime = (System.currentTimeMillis() / 1000) - 1 + res.lastUpdated = + if (parsedTime < currentTime) parsedTime else currentTime.toInt() + } + } + } + } + return res + } + + fun parseTime(timeS: String): Long { + try { + when { + timeS.endsWith(FEW_SECONDS_AGO) -> return System.currentTimeMillis() - 5000 + + timeS.endsWith(SECONDS_AGO_SUFFIX) -> { + val locStr = timeS.removeSuffix(SECONDS_AGO_SUFFIX) + return System.currentTimeMillis() - locStr.toLong() * 1000 + } + timeS.endsWith(MINUTES_AGO_SUFFIX) -> { + val locStr = timeS.removeSuffix(MINUTES_AGO_SUFFIX) + val minutes = locStr.toLong() + return System.currentTimeMillis() - minutes * 60 * 1000 + } + timeS.endsWith(HOURS_AGO_SUFFIX) -> { + val locStr = timeS.removeSuffix(HOURS_AGO_SUFFIX) + val hours = locStr.toLong() + return (System.currentTimeMillis() - hours * 60 * 60 * 1000) + } + timeS.endsWith(UTC_FORMAT_SUFFIX) -> { + val locStr = timeS.removeSuffix(UTC_FORMAT_SUFFIX) + val (latS, lonS) = locStr.split(" ") + val date = UTC_DATE_FORMAT.parse(latS) + val time = UTC_TIME_FORMAT.parse(lonS) + val res = date.time + time.time + return res + } + } + } catch (e: Exception) { + e.printStackTrace() + } + return 0 + } + + fun getTextMessageContent(updateId: Int, location: LocationMessage): TdApi.InputMessageText { + val entities = mutableListOf() + val builder = StringBuilder() + val locationMessage = formatLocation(location) + + val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ') + val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1) + entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK))) + builder.append("$USER_TEXT_LOCATION_TITLE\n") + + entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold())) + builder.append(LOCATION_PREFIX) + + entities.add(TdApi.TextEntity(builder.length, locationMessage.length, + TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.lat}&lon=${location.lon}"))) + builder.append("$locationMessage\n") + + if (location.altitude != 0.0) { + entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold())) + builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude)) + } + if (location.speed > 0) { + entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold())) + builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed)) + } + if (location.hdop != 0.0 && location.speed == 0.0) { + entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold())) + builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.hdop.toInt())) + } + if (updateId == 0) { + builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time))) + } else { + builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId)) + } + val textMessage = builder.toString().trim() + + return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true) + } + + fun getTextMessageContent(updateId: Int, location: BufferMessage): TdApi.InputMessageText { + val entities = mutableListOf() + val builder = StringBuilder() + val locationMessage = formatLocation(location) + + val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ') + val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1) + entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK))) + builder.append("$USER_TEXT_LOCATION_TITLE\n") + + entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold())) + builder.append(LOCATION_PREFIX) + + entities.add(TdApi.TextEntity(builder.length, locationMessage.length, + TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.lat}&lon=${location.lon}"))) + builder.append("$locationMessage\n") + + if (location.altitude != 0.0) { + entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold())) + builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude)) + } + if (location.speed > 0) { + entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold())) + builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed)) + } + if (location.hdop != 0.0 && location.speed == 0.0) { + entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold())) + builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.hdop.toInt())) + } + if (updateId == 0) { + builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time))) + } else { + builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId)) + } + val textMessage = builder.toString().trim() + + return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true) + } + + fun convertLocationMessagesToGpxFiles(items: List, newGpxPerChat: Boolean = true): List { + val dataTracks = ArrayList() + + var previousTime: Long = -1 + var previousChatId: Long = -1 + var previousUserId = -1 + var segment: GPXUtilities.TrkSegment? = null + var track: GPXUtilities.Track? = null + var gpx: GPXUtilities.GPXFile? = null + var countedLocations = 0 + + items.forEach { + val userId = it.userId + val chatId = it.chatId + val time = it.time + if (previousTime >= time) { + return@forEach + } + countedLocations++ + if (previousUserId != userId || (newGpxPerChat && previousChatId != chatId)) { + gpx = GPXUtilities.GPXFile() + gpx!!.chatId = chatId + gpx!!.userId = userId + previousTime = 0 + track = null + segment = null + dataTracks.add(gpx!!) + } + val pt = GPXUtilities.WptPt() + pt.userId = userId + pt.chatId = chatId + pt.lat = it.lat + pt.lon = it.lon + pt.ele = it.altitude + pt.speed = it.speed + pt.hdop = it.hdop + pt.time = time + val currentInterval = Math.abs(time - previousTime) + if (track != null) { + if (currentInterval < 30 * 60 * 1000) { + // 30 minute - same segment + segment!!.points.add(pt) + } else { + segment = GPXUtilities.TrkSegment() + segment!!.points.add(pt) + track!!.segments.add(segment) + } + } else { + track = GPXUtilities.Track() + segment = GPXUtilities.TrkSegment() + track!!.segments.add(segment) + segment!!.points.add(pt) + + gpx!!.tracks.add(track) + } + previousTime = time + previousUserId = userId + previousChatId = chatId + } + + return dataTracks + } + + fun saveGpx(app: TelegramApplication, listener: SaveGpxListener, dir: File, gpxFile: GPXUtilities.GPXFile) { + if (!gpxFile.isEmpty) { + val task = SaveGPXTrackToFileTask(app, listener, gpxFile, dir, 0) + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + } + } + + abstract class MessageLocation : TdApi.MessageContent() { + + var lat: Double = Double.NaN + internal set + var lon: Double = Double.NaN + internal set + var lastUpdated: Int = 0 + internal set + var speed: Double = 0.0 + internal set + var altitude: Double = 0.0 + internal set + var hdop: Double = 0.0 + internal set + var bearing: Double = 0.0 + internal set + var type: Int = -1 + internal set + + override fun getConstructor() = -1 + + abstract fun isValid(): Boolean + } + + class MessageOsmAndBotLocation : MessageLocation() { + + var name: String = "" + internal set + + override fun isValid() = name != "" && lat != Double.NaN && lon != Double.NaN + } + + class MessageUserLocation : MessageLocation() { + + override fun isValid() = lat != Double.NaN && lon != Double.NaN + + } + + private class SaveGPXTrackToFileTask internal constructor( + private val app: TelegramApplication, private val listener: SaveGpxListener?, + private val gpxFile: GPXUtilities.GPXFile, private val dir: File, private val userId: Int + ) : + AsyncTask>() { + + override fun doInBackground(vararg params: Void): List { + val warnings = ArrayList() + dir.mkdirs() + if (dir.parentFile.canWrite()) { + if (dir.exists()) { + // save file + var fout = File(dir, "$userId.gpx") + if (!gpxFile.isEmpty) { + val pt = gpxFile.findPointToShow() + + val user = app.telegramHelper.getUser(pt!!.userId) + val fileName: String + fileName = if (user != null) { + (TelegramUiHelper.getUserName(user) + "_" + SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date(pt.time))) + } else { + userId.toString() + "_" + SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date(pt.time)) + } + fout = File(dir, "$fileName.gpx") + } + val warn = GPXUtilities.writeGpxFile(fout, gpxFile, app) + if (warn != null) { + warnings.add(warn) + return warnings + } + } + } + + return warnings + } + + override fun onPostExecute(warnings: List?) { + if (listener != null) { + if (warnings != null && warnings.isEmpty()) { + listener.onSavingGpxFinish(gpxFile.path) + } else { + listener.onSavingGpxError(warnings) + } + } + } + } + + interface SaveGpxListener { + + fun onSavingGpxFinish(path: String) + + fun onSavingGpxError(warnings: List?) + } +} \ No newline at end of file diff --git a/OsmAnd/.gitignore b/OsmAnd/.gitignore index 5f59a8e51a..c75beb837e 100644 --- a/OsmAnd/.gitignore +++ b/OsmAnd/.gitignore @@ -22,6 +22,7 @@ assets/specialphrases/* assets/voice/* assets/fonts/* assets/feature_articles/* +assets/World_basemap_mini* gen/ local.properties raw/ diff --git a/OsmAnd/AndroidManifest.xml b/OsmAnd/AndroidManifest.xml index c0d2b707a7..b76a33db3e 100644 --- a/OsmAnd/AndroidManifest.xml +++ b/OsmAnd/AndroidManifest.xml @@ -39,7 +39,7 @@ - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/assets/bundled_assets.xml b/OsmAnd/assets/bundled_assets.xml index 4b0e2550e1..3cb6693083 100644 --- a/OsmAnd/assets/bundled_assets.xml +++ b/OsmAnd/assets/bundled_assets.xml @@ -21,7 +21,9 @@ + + @@ -49,8 +51,6 @@ - - @@ -60,4 +60,6 @@ + + diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index b0a4d3a82e..8695bd75c6 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -52,10 +52,10 @@ android { minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : (analytics ? 15 : 14) targetSdkVersion 26 - versionCode 320 + versionCode 330 versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode multiDexEnabled true - versionName "3.2.0" + versionName "3.3.0" versionName System.getenv("APK_VERSION")? System.getenv("APK_VERSION").toString(): versionName versionName System.getenv("APK_VERSION_SUFFIX")? versionName + System.getenv("APK_VERSION_SUFFIX").toString(): versionName } @@ -234,6 +234,12 @@ task validateTranslate { } } +task downloadWorldMiniBasemap { + doLast { + ant.get(src: 'http://builder.osmand.net/basemap/World_basemap_mini_2.obf', dest: 'assets/World_basemap_mini.obf', skipexisting: 'true') + } +} + task collectVoiceAssets(type: Sync) { from "../../resources/voice" into "assets/voice" @@ -292,13 +298,14 @@ task copyWidgetIcons(type: Exec) { task collectExternalResources { dependsOn collectVoiceAssets, - collectFonts, - collectHelpContentsAssets, - collectHelpContentsStyle, - copyStyleIcons, - updateNoTranslate, - validateTranslate, - copyWidgetIcons + collectFonts, + collectHelpContentsAssets, + collectHelpContentsStyle, + copyStyleIcons, + updateNoTranslate, + validateTranslate, + copyWidgetIcons, + downloadWorldMiniBasemap } // Legacy core build diff --git a/OsmAnd/no_translate.xml b/OsmAnd/no_translate.xml index 4f310b48ec..be266d95a0 100644 --- a/OsmAnd/no_translate.xml +++ b/OsmAnd/no_translate.xml @@ -24,7 +24,12 @@ Tag Value - €1,49 + €1,49 + €1,99 + €3,99 + €1,33 + €7,99 + €2,66 https://twitter.com/osmandapp https://www.facebook.com/osmandapp https://vk.com/osmandapp diff --git a/OsmAnd/res/drawable-hdpi/ic_action_allow_private_access.png b/OsmAnd/res/drawable-hdpi/ic_action_allow_private_access.png new file mode 100644 index 0000000000..e0fd9cf302 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_allow_private_access.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_avoid_motorways.png b/OsmAnd/res/drawable-hdpi/ic_action_avoid_motorways.png new file mode 100644 index 0000000000..7820b1e9e5 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_avoid_motorways.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_edit_dark.png b/OsmAnd/res/drawable-hdpi/ic_action_edit_dark.png index 7a84c52ecc..bff0bdb1a9 100644 Binary files a/OsmAnd/res/drawable-hdpi/ic_action_edit_dark.png and b/OsmAnd/res/drawable-hdpi/ic_action_edit_dark.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_elevation.png b/OsmAnd/res/drawable-hdpi/ic_action_elevation.png new file mode 100644 index 0000000000..5f6e69aa61 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_elevation.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_forbid_private_access.png b/OsmAnd/res/drawable-hdpi/ic_action_forbid_private_access.png new file mode 100644 index 0000000000..bf0de8b1bd Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_forbid_private_access.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_fuel.png b/OsmAnd/res/drawable-hdpi/ic_action_fuel.png new file mode 100644 index 0000000000..cfa278c5fe Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_fuel.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_item_move.png b/OsmAnd/res/drawable-hdpi/ic_action_item_move.png new file mode 100644 index 0000000000..beb5146138 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_item_move.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_motorways.png b/OsmAnd/res/drawable-hdpi/ic_action_motorways.png new file mode 100644 index 0000000000..d1f9d0cab7 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_motorways.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_nautical_depth.png b/OsmAnd/res/drawable-hdpi/ic_action_nautical_depth.png new file mode 100644 index 0000000000..2be1fb6d06 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_nautical_depth.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_road_works_dark.png b/OsmAnd/res/drawable-hdpi/ic_action_road_works_dark.png index fba92868eb..ac26add075 100644 Binary files a/OsmAnd/res/drawable-hdpi/ic_action_road_works_dark.png and b/OsmAnd/res/drawable-hdpi/ic_action_road_works_dark.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_sand_clock.png b/OsmAnd/res/drawable-hdpi/ic_action_sand_clock.png new file mode 100644 index 0000000000..5aacc19165 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_sand_clock.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_show_along_route.png b/OsmAnd/res/drawable-hdpi/ic_action_show_along_route.png new file mode 100644 index 0000000000..1afaf4f922 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_show_along_route.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_start_navigation.png b/OsmAnd/res/drawable-hdpi/ic_action_start_navigation.png index f858899c7a..05f1981a06 100644 Binary files a/OsmAnd/res/drawable-hdpi/ic_action_start_navigation.png and b/OsmAnd/res/drawable-hdpi/ic_action_start_navigation.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_unlimited_download.png b/OsmAnd/res/drawable-hdpi/ic_action_unlimited_download.png new file mode 100644 index 0000000000..6ec40bdb79 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_unlimited_download.png differ diff --git a/OsmAnd/res/drawable-hdpi/ic_action_volume_mute.png b/OsmAnd/res/drawable-hdpi/ic_action_volume_mute.png new file mode 100644 index 0000000000..5d54c893d4 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/ic_action_volume_mute.png differ diff --git a/OsmAnd/res/drawable-hdpi/map_nautical_bearing.png b/OsmAnd/res/drawable-hdpi/map_nautical_bearing.png new file mode 100644 index 0000000000..72e427c437 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/map_nautical_bearing.png differ diff --git a/OsmAnd/res/drawable-hdpi/map_nautical_bearing_night.png b/OsmAnd/res/drawable-hdpi/map_nautical_bearing_night.png new file mode 100644 index 0000000000..c7d4e436be Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/map_nautical_bearing_night.png differ diff --git a/OsmAnd/res/drawable-hdpi/map_nautical_location.png b/OsmAnd/res/drawable-hdpi/map_nautical_location.png new file mode 100644 index 0000000000..4a8b8aebc5 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/map_nautical_location.png differ diff --git a/OsmAnd/res/drawable-hdpi/map_nautical_location_night.png b/OsmAnd/res/drawable-hdpi/map_nautical_location_night.png new file mode 100644 index 0000000000..80baedcf73 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/map_nautical_location_night.png differ diff --git a/OsmAnd/res/drawable-hdpi/map_nautical_location_view_angle.png b/OsmAnd/res/drawable-hdpi/map_nautical_location_view_angle.png new file mode 100644 index 0000000000..6940b54e60 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/map_nautical_location_view_angle.png differ diff --git a/OsmAnd/res/drawable-hdpi/map_nautical_location_view_angle_night.png b/OsmAnd/res/drawable-hdpi/map_nautical_location_view_angle_night.png new file mode 100644 index 0000000000..744054c178 Binary files /dev/null and b/OsmAnd/res/drawable-hdpi/map_nautical_location_view_angle_night.png differ diff --git a/OsmAnd/res/drawable-hdpi/map_start_navigation.png b/OsmAnd/res/drawable-hdpi/map_start_navigation.png index f858899c7a..9ab5382df1 100644 Binary files a/OsmAnd/res/drawable-hdpi/map_start_navigation.png and b/OsmAnd/res/drawable-hdpi/map_start_navigation.png differ diff --git a/OsmAnd/res/drawable-large-hdpi/map_nautical_bearing.png b/OsmAnd/res/drawable-large-hdpi/map_nautical_bearing.png new file mode 100644 index 0000000000..84492536d2 Binary files /dev/null and b/OsmAnd/res/drawable-large-hdpi/map_nautical_bearing.png differ diff --git a/OsmAnd/res/drawable-large-hdpi/map_nautical_bearing_night.png b/OsmAnd/res/drawable-large-hdpi/map_nautical_bearing_night.png new file mode 100644 index 0000000000..4a5d566a9f Binary files /dev/null and b/OsmAnd/res/drawable-large-hdpi/map_nautical_bearing_night.png differ diff --git a/OsmAnd/res/drawable-large-hdpi/map_nautical_location.png b/OsmAnd/res/drawable-large-hdpi/map_nautical_location.png new file mode 100644 index 0000000000..eb4ee41d99 Binary files /dev/null and b/OsmAnd/res/drawable-large-hdpi/map_nautical_location.png differ diff --git a/OsmAnd/res/drawable-large-hdpi/map_nautical_location_night.png b/OsmAnd/res/drawable-large-hdpi/map_nautical_location_night.png new file mode 100644 index 0000000000..5925133490 Binary files /dev/null and b/OsmAnd/res/drawable-large-hdpi/map_nautical_location_night.png differ diff --git a/OsmAnd/res/drawable-large-hdpi/map_nautical_location_view_angle.png b/OsmAnd/res/drawable-large-hdpi/map_nautical_location_view_angle.png new file mode 100644 index 0000000000..db4d3d4a75 Binary files /dev/null and b/OsmAnd/res/drawable-large-hdpi/map_nautical_location_view_angle.png differ diff --git a/OsmAnd/res/drawable-large-hdpi/map_nautical_location_view_angle_night.png b/OsmAnd/res/drawable-large-hdpi/map_nautical_location_view_angle_night.png new file mode 100644 index 0000000000..908e52b411 Binary files /dev/null and b/OsmAnd/res/drawable-large-hdpi/map_nautical_location_view_angle_night.png differ diff --git a/OsmAnd/res/drawable-large-hdpi/map_start_navigation.png b/OsmAnd/res/drawable-large-hdpi/map_start_navigation.png index 50e6999757..3e784bf91e 100644 Binary files a/OsmAnd/res/drawable-large-hdpi/map_start_navigation.png and b/OsmAnd/res/drawable-large-hdpi/map_start_navigation.png differ diff --git a/OsmAnd/res/drawable-large-xhdpi/map_nautical_bearing.png b/OsmAnd/res/drawable-large-xhdpi/map_nautical_bearing.png new file mode 100644 index 0000000000..01533ae4f7 Binary files /dev/null and b/OsmAnd/res/drawable-large-xhdpi/map_nautical_bearing.png differ diff --git a/OsmAnd/res/drawable-large-xhdpi/map_nautical_bearing_night.png b/OsmAnd/res/drawable-large-xhdpi/map_nautical_bearing_night.png new file mode 100644 index 0000000000..e6687ff69a Binary files /dev/null and b/OsmAnd/res/drawable-large-xhdpi/map_nautical_bearing_night.png differ diff --git a/OsmAnd/res/drawable-large-xhdpi/map_nautical_location.png b/OsmAnd/res/drawable-large-xhdpi/map_nautical_location.png new file mode 100644 index 0000000000..4e19b38778 Binary files /dev/null and b/OsmAnd/res/drawable-large-xhdpi/map_nautical_location.png differ diff --git a/OsmAnd/res/drawable-large-xhdpi/map_nautical_location_night.png b/OsmAnd/res/drawable-large-xhdpi/map_nautical_location_night.png new file mode 100644 index 0000000000..8800e0c2b8 Binary files /dev/null and b/OsmAnd/res/drawable-large-xhdpi/map_nautical_location_night.png differ diff --git a/OsmAnd/res/drawable-large-xhdpi/map_nautical_location_view_angle.png b/OsmAnd/res/drawable-large-xhdpi/map_nautical_location_view_angle.png new file mode 100644 index 0000000000..28f4b540fe Binary files /dev/null and b/OsmAnd/res/drawable-large-xhdpi/map_nautical_location_view_angle.png differ diff --git a/OsmAnd/res/drawable-large-xhdpi/map_nautical_location_view_angle_night.png b/OsmAnd/res/drawable-large-xhdpi/map_nautical_location_view_angle_night.png new file mode 100644 index 0000000000..c1b505218b Binary files /dev/null and b/OsmAnd/res/drawable-large-xhdpi/map_nautical_location_view_angle_night.png differ diff --git a/OsmAnd/res/drawable-large-xhdpi/map_start_navigation.png b/OsmAnd/res/drawable-large-xhdpi/map_start_navigation.png index 6445f91337..ee62bbac20 100644 Binary files a/OsmAnd/res/drawable-large-xhdpi/map_start_navigation.png and b/OsmAnd/res/drawable-large-xhdpi/map_start_navigation.png differ diff --git a/OsmAnd/res/drawable-large/map_nautical_bearing.png b/OsmAnd/res/drawable-large/map_nautical_bearing.png new file mode 100644 index 0000000000..72e427c437 Binary files /dev/null and b/OsmAnd/res/drawable-large/map_nautical_bearing.png differ diff --git a/OsmAnd/res/drawable-large/map_nautical_bearing_night.png b/OsmAnd/res/drawable-large/map_nautical_bearing_night.png new file mode 100644 index 0000000000..c7d4e436be Binary files /dev/null and b/OsmAnd/res/drawable-large/map_nautical_bearing_night.png differ diff --git a/OsmAnd/res/drawable-large/map_nautical_location.png b/OsmAnd/res/drawable-large/map_nautical_location.png new file mode 100644 index 0000000000..4a8b8aebc5 Binary files /dev/null and b/OsmAnd/res/drawable-large/map_nautical_location.png differ diff --git a/OsmAnd/res/drawable-large/map_nautical_location_night.png b/OsmAnd/res/drawable-large/map_nautical_location_night.png new file mode 100644 index 0000000000..80baedcf73 Binary files /dev/null and b/OsmAnd/res/drawable-large/map_nautical_location_night.png differ diff --git a/OsmAnd/res/drawable-large/map_nautical_location_view_angle.png b/OsmAnd/res/drawable-large/map_nautical_location_view_angle.png new file mode 100644 index 0000000000..6940b54e60 Binary files /dev/null and b/OsmAnd/res/drawable-large/map_nautical_location_view_angle.png differ diff --git a/OsmAnd/res/drawable-large/map_nautical_location_view_angle_night.png b/OsmAnd/res/drawable-large/map_nautical_location_view_angle_night.png new file mode 100644 index 0000000000..744054c178 Binary files /dev/null and b/OsmAnd/res/drawable-large/map_nautical_location_view_angle_night.png differ diff --git a/OsmAnd/res/drawable-large/map_start_navigation.png b/OsmAnd/res/drawable-large/map_start_navigation.png index f858899c7a..9ab5382df1 100644 Binary files a/OsmAnd/res/drawable-large/map_start_navigation.png and b/OsmAnd/res/drawable-large/map_start_navigation.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_allow_private_access.png b/OsmAnd/res/drawable-mdpi/ic_action_allow_private_access.png new file mode 100644 index 0000000000..500496b736 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_allow_private_access.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_avoid_motorways.png b/OsmAnd/res/drawable-mdpi/ic_action_avoid_motorways.png new file mode 100644 index 0000000000..643f3d8b60 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_avoid_motorways.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_edit_dark.png b/OsmAnd/res/drawable-mdpi/ic_action_edit_dark.png index d1b7340b56..2ad91ead2e 100644 Binary files a/OsmAnd/res/drawable-mdpi/ic_action_edit_dark.png and b/OsmAnd/res/drawable-mdpi/ic_action_edit_dark.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_elevation.png b/OsmAnd/res/drawable-mdpi/ic_action_elevation.png new file mode 100644 index 0000000000..c5419d9164 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_elevation.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_forbid_private_access.png b/OsmAnd/res/drawable-mdpi/ic_action_forbid_private_access.png new file mode 100644 index 0000000000..4b7db66c7f Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_forbid_private_access.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_fuel.png b/OsmAnd/res/drawable-mdpi/ic_action_fuel.png new file mode 100644 index 0000000000..d3d12983d2 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_fuel.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_item_move.png b/OsmAnd/res/drawable-mdpi/ic_action_item_move.png new file mode 100644 index 0000000000..f3b8367a18 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_item_move.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_motorways.png b/OsmAnd/res/drawable-mdpi/ic_action_motorways.png new file mode 100644 index 0000000000..cb91768a09 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_motorways.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_nautical_depth.png b/OsmAnd/res/drawable-mdpi/ic_action_nautical_depth.png new file mode 100644 index 0000000000..536f58dac3 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_nautical_depth.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_road_works_dark.png b/OsmAnd/res/drawable-mdpi/ic_action_road_works_dark.png index 3fb1c2cd90..72417b25f5 100644 Binary files a/OsmAnd/res/drawable-mdpi/ic_action_road_works_dark.png and b/OsmAnd/res/drawable-mdpi/ic_action_road_works_dark.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_sand_clock.png b/OsmAnd/res/drawable-mdpi/ic_action_sand_clock.png new file mode 100644 index 0000000000..a0590dab76 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_sand_clock.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_show_along_route.png b/OsmAnd/res/drawable-mdpi/ic_action_show_along_route.png new file mode 100644 index 0000000000..59994b22f8 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_show_along_route.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_start_navigation.png b/OsmAnd/res/drawable-mdpi/ic_action_start_navigation.png index 3ad15dbab7..d145134e9e 100644 Binary files a/OsmAnd/res/drawable-mdpi/ic_action_start_navigation.png and b/OsmAnd/res/drawable-mdpi/ic_action_start_navigation.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_unlimited_download.png b/OsmAnd/res/drawable-mdpi/ic_action_unlimited_download.png new file mode 100644 index 0000000000..01cbc6aa57 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_unlimited_download.png differ diff --git a/OsmAnd/res/drawable-mdpi/ic_action_volume_mute.png b/OsmAnd/res/drawable-mdpi/ic_action_volume_mute.png new file mode 100644 index 0000000000..f7341e3544 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/ic_action_volume_mute.png differ diff --git a/OsmAnd/res/drawable-mdpi/map_nautical_bearing.png b/OsmAnd/res/drawable-mdpi/map_nautical_bearing.png new file mode 100644 index 0000000000..f5226ce658 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/map_nautical_bearing.png differ diff --git a/OsmAnd/res/drawable-mdpi/map_nautical_bearing_night.png b/OsmAnd/res/drawable-mdpi/map_nautical_bearing_night.png new file mode 100644 index 0000000000..1cac77b7c5 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/map_nautical_bearing_night.png differ diff --git a/OsmAnd/res/drawable-mdpi/map_nautical_location.png b/OsmAnd/res/drawable-mdpi/map_nautical_location.png new file mode 100644 index 0000000000..ccf25980c1 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/map_nautical_location.png differ diff --git a/OsmAnd/res/drawable-mdpi/map_nautical_location_night.png b/OsmAnd/res/drawable-mdpi/map_nautical_location_night.png new file mode 100644 index 0000000000..0ab169798e Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/map_nautical_location_night.png differ diff --git a/OsmAnd/res/drawable-mdpi/map_nautical_location_view_angle.png b/OsmAnd/res/drawable-mdpi/map_nautical_location_view_angle.png new file mode 100644 index 0000000000..ec345eb578 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/map_nautical_location_view_angle.png differ diff --git a/OsmAnd/res/drawable-mdpi/map_nautical_location_view_angle_night.png b/OsmAnd/res/drawable-mdpi/map_nautical_location_view_angle_night.png new file mode 100644 index 0000000000..b51a6fcee2 Binary files /dev/null and b/OsmAnd/res/drawable-mdpi/map_nautical_location_view_angle_night.png differ diff --git a/OsmAnd/res/drawable-mdpi/map_start_navigation.png b/OsmAnd/res/drawable-mdpi/map_start_navigation.png index 3ad15dbab7..226a412587 100644 Binary files a/OsmAnd/res/drawable-mdpi/map_start_navigation.png and b/OsmAnd/res/drawable-mdpi/map_start_navigation.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_allow_private_access.png b/OsmAnd/res/drawable-xhdpi/ic_action_allow_private_access.png new file mode 100644 index 0000000000..1996777306 Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_allow_private_access.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_avoid_motorways.png b/OsmAnd/res/drawable-xhdpi/ic_action_avoid_motorways.png new file mode 100644 index 0000000000..7afa47fcdc Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_avoid_motorways.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_edit_dark.png b/OsmAnd/res/drawable-xhdpi/ic_action_edit_dark.png index 65f24663bc..66ab8968fa 100644 Binary files a/OsmAnd/res/drawable-xhdpi/ic_action_edit_dark.png and b/OsmAnd/res/drawable-xhdpi/ic_action_edit_dark.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_elevation.png b/OsmAnd/res/drawable-xhdpi/ic_action_elevation.png new file mode 100644 index 0000000000..0d095c40a3 Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_elevation.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_forbid_private_access.png b/OsmAnd/res/drawable-xhdpi/ic_action_forbid_private_access.png new file mode 100644 index 0000000000..88017d5efe Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_forbid_private_access.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_fuel.png b/OsmAnd/res/drawable-xhdpi/ic_action_fuel.png new file mode 100644 index 0000000000..c50722ba24 Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_fuel.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_item_move.png b/OsmAnd/res/drawable-xhdpi/ic_action_item_move.png new file mode 100644 index 0000000000..7bc48814a7 Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_item_move.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_motorways.png b/OsmAnd/res/drawable-xhdpi/ic_action_motorways.png new file mode 100644 index 0000000000..c431d5712d Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_motorways.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_nautical_depth.png b/OsmAnd/res/drawable-xhdpi/ic_action_nautical_depth.png new file mode 100644 index 0000000000..ece5b9af0e Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_nautical_depth.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_road_works_dark.png b/OsmAnd/res/drawable-xhdpi/ic_action_road_works_dark.png index 7ad11bc01e..8e71595943 100644 Binary files a/OsmAnd/res/drawable-xhdpi/ic_action_road_works_dark.png and b/OsmAnd/res/drawable-xhdpi/ic_action_road_works_dark.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_sand_clock.png b/OsmAnd/res/drawable-xhdpi/ic_action_sand_clock.png new file mode 100644 index 0000000000..e92ee34e30 Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_sand_clock.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_show_along_route.png b/OsmAnd/res/drawable-xhdpi/ic_action_show_along_route.png new file mode 100644 index 0000000000..4e021d54cc Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_show_along_route.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_start_navigation.png b/OsmAnd/res/drawable-xhdpi/ic_action_start_navigation.png index 50e6999757..9b74a47019 100644 Binary files a/OsmAnd/res/drawable-xhdpi/ic_action_start_navigation.png and b/OsmAnd/res/drawable-xhdpi/ic_action_start_navigation.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_unlimited_download.png b/OsmAnd/res/drawable-xhdpi/ic_action_unlimited_download.png new file mode 100644 index 0000000000..ae38e9711d Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_unlimited_download.png differ diff --git a/OsmAnd/res/drawable-xhdpi/ic_action_volume_mute.png b/OsmAnd/res/drawable-xhdpi/ic_action_volume_mute.png new file mode 100644 index 0000000000..4a81fff637 Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/ic_action_volume_mute.png differ diff --git a/OsmAnd/res/drawable-xhdpi/map_nautical_bearing.png b/OsmAnd/res/drawable-xhdpi/map_nautical_bearing.png new file mode 100644 index 0000000000..84492536d2 Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/map_nautical_bearing.png differ diff --git a/OsmAnd/res/drawable-xhdpi/map_nautical_bearing_night.png b/OsmAnd/res/drawable-xhdpi/map_nautical_bearing_night.png new file mode 100644 index 0000000000..4a5d566a9f Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/map_nautical_bearing_night.png differ diff --git a/OsmAnd/res/drawable-xhdpi/map_nautical_location.png b/OsmAnd/res/drawable-xhdpi/map_nautical_location.png new file mode 100644 index 0000000000..eb4ee41d99 Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/map_nautical_location.png differ diff --git a/OsmAnd/res/drawable-xhdpi/map_nautical_location_night.png b/OsmAnd/res/drawable-xhdpi/map_nautical_location_night.png new file mode 100644 index 0000000000..5925133490 Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/map_nautical_location_night.png differ diff --git a/OsmAnd/res/drawable-xhdpi/map_nautical_location_view_angle.png b/OsmAnd/res/drawable-xhdpi/map_nautical_location_view_angle.png new file mode 100644 index 0000000000..db4d3d4a75 Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/map_nautical_location_view_angle.png differ diff --git a/OsmAnd/res/drawable-xhdpi/map_nautical_location_view_angle_night.png b/OsmAnd/res/drawable-xhdpi/map_nautical_location_view_angle_night.png new file mode 100644 index 0000000000..908e52b411 Binary files /dev/null and b/OsmAnd/res/drawable-xhdpi/map_nautical_location_view_angle_night.png differ diff --git a/OsmAnd/res/drawable-xhdpi/map_start_navigation.png b/OsmAnd/res/drawable-xhdpi/map_start_navigation.png index 50e6999757..3e784bf91e 100644 Binary files a/OsmAnd/res/drawable-xhdpi/map_start_navigation.png and b/OsmAnd/res/drawable-xhdpi/map_start_navigation.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_allow_private_access.png b/OsmAnd/res/drawable-xxhdpi/ic_action_allow_private_access.png new file mode 100644 index 0000000000..908703d273 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_allow_private_access.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_avoid_motorways.png b/OsmAnd/res/drawable-xxhdpi/ic_action_avoid_motorways.png new file mode 100644 index 0000000000..41e01d50dd Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_avoid_motorways.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_edit_dark.png b/OsmAnd/res/drawable-xxhdpi/ic_action_edit_dark.png index 42eec822b5..f13f0e3278 100644 Binary files a/OsmAnd/res/drawable-xxhdpi/ic_action_edit_dark.png and b/OsmAnd/res/drawable-xxhdpi/ic_action_edit_dark.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_elevation.png b/OsmAnd/res/drawable-xxhdpi/ic_action_elevation.png new file mode 100644 index 0000000000..2214ab9476 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_elevation.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_forbid_private_access.png b/OsmAnd/res/drawable-xxhdpi/ic_action_forbid_private_access.png new file mode 100644 index 0000000000..d28c4f3910 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_forbid_private_access.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_fuel.png b/OsmAnd/res/drawable-xxhdpi/ic_action_fuel.png new file mode 100644 index 0000000000..ca1e394930 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_fuel.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_item_move.png b/OsmAnd/res/drawable-xxhdpi/ic_action_item_move.png new file mode 100644 index 0000000000..da3f33b6b5 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_item_move.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_motorways.png b/OsmAnd/res/drawable-xxhdpi/ic_action_motorways.png new file mode 100644 index 0000000000..1d010a4f3b Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_motorways.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_nautical_depth.png b/OsmAnd/res/drawable-xxhdpi/ic_action_nautical_depth.png new file mode 100644 index 0000000000..9ef52a6519 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_nautical_depth.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_road_works_dark.png b/OsmAnd/res/drawable-xxhdpi/ic_action_road_works_dark.png index 3cc2e11963..b98e85fc29 100644 Binary files a/OsmAnd/res/drawable-xxhdpi/ic_action_road_works_dark.png and b/OsmAnd/res/drawable-xxhdpi/ic_action_road_works_dark.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_sand_clock.png b/OsmAnd/res/drawable-xxhdpi/ic_action_sand_clock.png new file mode 100644 index 0000000000..afb35668ca Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_sand_clock.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_show_along_route.png b/OsmAnd/res/drawable-xxhdpi/ic_action_show_along_route.png new file mode 100644 index 0000000000..3ab4c55809 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_show_along_route.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_start_navigation.png b/OsmAnd/res/drawable-xxhdpi/ic_action_start_navigation.png index 6445f91337..cf8fd2ed24 100644 Binary files a/OsmAnd/res/drawable-xxhdpi/ic_action_start_navigation.png and b/OsmAnd/res/drawable-xxhdpi/ic_action_start_navigation.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_unlimited_download.png b/OsmAnd/res/drawable-xxhdpi/ic_action_unlimited_download.png new file mode 100644 index 0000000000..cf83cd4f81 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_unlimited_download.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/ic_action_volume_mute.png b/OsmAnd/res/drawable-xxhdpi/ic_action_volume_mute.png new file mode 100644 index 0000000000..9c832cbbf6 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/ic_action_volume_mute.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/map_nautical_bearing.png b/OsmAnd/res/drawable-xxhdpi/map_nautical_bearing.png new file mode 100644 index 0000000000..01533ae4f7 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/map_nautical_bearing.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/map_nautical_bearing_night.png b/OsmAnd/res/drawable-xxhdpi/map_nautical_bearing_night.png new file mode 100644 index 0000000000..e6687ff69a Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/map_nautical_bearing_night.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/map_nautical_location.png b/OsmAnd/res/drawable-xxhdpi/map_nautical_location.png new file mode 100644 index 0000000000..4e19b38778 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/map_nautical_location.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/map_nautical_location_night.png b/OsmAnd/res/drawable-xxhdpi/map_nautical_location_night.png new file mode 100644 index 0000000000..8800e0c2b8 Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/map_nautical_location_night.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/map_nautical_location_view_angle.png b/OsmAnd/res/drawable-xxhdpi/map_nautical_location_view_angle.png new file mode 100644 index 0000000000..28f4b540fe Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/map_nautical_location_view_angle.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/map_nautical_location_view_angle_night.png b/OsmAnd/res/drawable-xxhdpi/map_nautical_location_view_angle_night.png new file mode 100644 index 0000000000..c1b505218b Binary files /dev/null and b/OsmAnd/res/drawable-xxhdpi/map_nautical_location_view_angle_night.png differ diff --git a/OsmAnd/res/drawable-xxhdpi/map_start_navigation.png b/OsmAnd/res/drawable-xxhdpi/map_start_navigation.png index 6445f91337..ee62bbac20 100644 Binary files a/OsmAnd/res/drawable-xxhdpi/map_start_navigation.png and b/OsmAnd/res/drawable-xxhdpi/map_start_navigation.png differ diff --git a/OsmAnd/res/drawable/btn_border_active_dark.xml b/OsmAnd/res/drawable/btn_border_active_dark.xml new file mode 100644 index 0000000000..7bfff2de9a --- /dev/null +++ b/OsmAnd/res/drawable/btn_border_active_dark.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/btn_border_active_light.xml b/OsmAnd/res/drawable/btn_border_active_light.xml new file mode 100644 index 0000000000..52999f4b4c --- /dev/null +++ b/OsmAnd/res/drawable/btn_border_active_light.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/btn_border_pressed_dark.xml b/OsmAnd/res/drawable/btn_border_pressed_dark.xml new file mode 100644 index 0000000000..3bbe4043e9 --- /dev/null +++ b/OsmAnd/res/drawable/btn_border_pressed_dark.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/btn_border_pressed_light.xml b/OsmAnd/res/drawable/btn_border_pressed_light.xml new file mode 100644 index 0000000000..12e9738e62 --- /dev/null +++ b/OsmAnd/res/drawable/btn_border_pressed_light.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/btn_border_trans_dark.xml b/OsmAnd/res/drawable/btn_border_trans_dark.xml new file mode 100644 index 0000000000..f50714a963 --- /dev/null +++ b/OsmAnd/res/drawable/btn_border_trans_dark.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/btn_border_trans_light.xml b/OsmAnd/res/drawable/btn_border_trans_light.xml new file mode 100644 index 0000000000..5c262a9204 --- /dev/null +++ b/OsmAnd/res/drawable/btn_border_trans_light.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/btn_border_trans_rounded_dark.xml b/OsmAnd/res/drawable/btn_border_trans_rounded_dark.xml new file mode 100644 index 0000000000..3f68423d61 --- /dev/null +++ b/OsmAnd/res/drawable/btn_border_trans_rounded_dark.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/btn_border_trans_rounded_light.xml b/OsmAnd/res/drawable/btn_border_trans_rounded_light.xml new file mode 100644 index 0000000000..aee8c9de9d --- /dev/null +++ b/OsmAnd/res/drawable/btn_border_trans_rounded_light.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/btn_round_border_dark_2.xml b/OsmAnd/res/drawable/btn_round_border_dark_2.xml new file mode 100644 index 0000000000..5e6a95c5ca --- /dev/null +++ b/OsmAnd/res/drawable/btn_round_border_dark_2.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/btn_round_border_light_2.xml b/OsmAnd/res/drawable/btn_round_border_light_2.xml new file mode 100644 index 0000000000..28b70eaacb --- /dev/null +++ b/OsmAnd/res/drawable/btn_round_border_light_2.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/context_menu_controller_disabled_bg_dark.xml b/OsmAnd/res/drawable/context_menu_controller_disabled_bg_dark.xml index 33274d8ff8..5738125226 100644 --- a/OsmAnd/res/drawable/context_menu_controller_disabled_bg_dark.xml +++ b/OsmAnd/res/drawable/context_menu_controller_disabled_bg_dark.xml @@ -6,7 +6,7 @@ + android:color="@color/ctx_menu_buttons_divider_dark" /> diff --git a/OsmAnd/res/drawable/context_menu_controller_disabled_bg_light.xml b/OsmAnd/res/drawable/context_menu_controller_disabled_bg_light.xml index 4ac0d70116..292955d5c4 100644 --- a/OsmAnd/res/drawable/context_menu_controller_disabled_bg_light.xml +++ b/OsmAnd/res/drawable/context_menu_controller_disabled_bg_light.xml @@ -6,7 +6,7 @@ + android:color="@color/ctx_menu_buttons_divider_light" /> diff --git a/OsmAnd/res/drawable/ripple_dark.xml b/OsmAnd/res/drawable/ripple_dark.xml new file mode 100644 index 0000000000..ce5672d46c --- /dev/null +++ b/OsmAnd/res/drawable/ripple_dark.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/ripple_light.xml b/OsmAnd/res/drawable/ripple_light.xml new file mode 100644 index 0000000000..f4a9855f6b --- /dev/null +++ b/OsmAnd/res/drawable/ripple_light.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/ripple_rounded_dark.xml b/OsmAnd/res/drawable/ripple_rounded_dark.xml new file mode 100644 index 0000000000..dc86d03171 --- /dev/null +++ b/OsmAnd/res/drawable/ripple_rounded_dark.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/ripple_rounded_light.xml b/OsmAnd/res/drawable/ripple_rounded_light.xml new file mode 100644 index 0000000000..a4a056ac0c --- /dev/null +++ b/OsmAnd/res/drawable/ripple_rounded_light.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/route_cards_topsides_light.xml b/OsmAnd/res/drawable/route_cards_topsides_light.xml new file mode 100644 index 0000000000..ebe1ef8a27 --- /dev/null +++ b/OsmAnd/res/drawable/route_cards_topsides_light.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/route_info_trans_gradient_dark.xml b/OsmAnd/res/drawable/route_info_trans_gradient_dark.xml new file mode 100644 index 0000000000..cf3653787d --- /dev/null +++ b/OsmAnd/res/drawable/route_info_trans_gradient_dark.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/route_info_trans_gradient_light.xml b/OsmAnd/res/drawable/route_info_trans_gradient_light.xml new file mode 100644 index 0000000000..76dd06c92e --- /dev/null +++ b/OsmAnd/res/drawable/route_info_trans_gradient_light.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/text_rounded_bg_active_dark.xml b/OsmAnd/res/drawable/text_rounded_bg_active_dark.xml new file mode 100644 index 0000000000..f5de3e9de0 --- /dev/null +++ b/OsmAnd/res/drawable/text_rounded_bg_active_dark.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/text_rounded_bg_active_light.xml b/OsmAnd/res/drawable/text_rounded_bg_active_light.xml new file mode 100644 index 0000000000..7d69b928f8 --- /dev/null +++ b/OsmAnd/res/drawable/text_rounded_bg_active_light.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/text_rounded_bg_regular_dark.xml b/OsmAnd/res/drawable/text_rounded_bg_regular_dark.xml new file mode 100644 index 0000000000..17ace62642 --- /dev/null +++ b/OsmAnd/res/drawable/text_rounded_bg_regular_dark.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/OsmAnd/res/drawable/text_rounded_bg_regular_light.xml b/OsmAnd/res/drawable/text_rounded_bg_regular_light.xml new file mode 100644 index 0000000000..68edc5de9d --- /dev/null +++ b/OsmAnd/res/drawable/text_rounded_bg_regular_light.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout-land/map_hud_bottom.xml b/OsmAnd/res/layout-land/map_hud_bottom.xml index 680be73185..53cdff7392 100644 --- a/OsmAnd/res/layout-land/map_hud_bottom.xml +++ b/OsmAnd/res/layout-land/map_hud_bottom.xml @@ -183,14 +183,6 @@ android:layout_height="wrap_content" android:layout_gravity="center"/> - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/OsmAnd/res/layout/along_the_route_category_item.xml b/OsmAnd/res/layout/along_the_route_category_item.xml new file mode 100644 index 0000000000..61887fd5d0 --- /dev/null +++ b/OsmAnd/res/layout/along_the_route_category_item.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/layout/along_the_route_point_item.xml b/OsmAnd/res/layout/along_the_route_point_item.xml new file mode 100644 index 0000000000..b5b816d5ab --- /dev/null +++ b/OsmAnd/res/layout/along_the_route_point_item.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/layout/along_the_route_radius_poi.xml b/OsmAnd/res/layout/along_the_route_radius_poi.xml new file mode 100644 index 0000000000..2ab9fb0a11 --- /dev/null +++ b/OsmAnd/res/layout/along_the_route_radius_poi.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/along_the_route_radius_simple.xml b/OsmAnd/res/layout/along_the_route_radius_simple.xml new file mode 100644 index 0000000000..7012dc3308 --- /dev/null +++ b/OsmAnd/res/layout/along_the_route_radius_simple.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_double_item.xml b/OsmAnd/res/layout/bottom_sheet_double_item.xml new file mode 100644 index 0000000000..af9d8a73fe --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_double_item.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/layout/bottom_sheet_item_btn.xml b/OsmAnd/res/layout/bottom_sheet_item_btn.xml new file mode 100644 index 0000000000..8664423c6e --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_item_btn.xml @@ -0,0 +1,38 @@ + + + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_item_simple_56dp.xml b/OsmAnd/res/layout/bottom_sheet_item_simple_56dp.xml new file mode 100644 index 0000000000..40afcad7c7 --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_item_simple_56dp.xml @@ -0,0 +1,34 @@ + + + + + + + + diff --git a/OsmAnd/res/layout/bottom_sheet_item_simple_right_icon.xml b/OsmAnd/res/layout/bottom_sheet_item_simple_right_icon.xml new file mode 100644 index 0000000000..1a74dd281a --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_item_simple_right_icon.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_item_title_long.xml b/OsmAnd/res/layout/bottom_sheet_item_title_long.xml new file mode 100644 index 0000000000..749f155f79 --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_item_title_long.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_item_toolbar_title.xml b/OsmAnd/res/layout/bottom_sheet_item_toolbar_title.xml new file mode 100644 index 0000000000..98fdf1267a --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_item_toolbar_title.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_item_with_switch_56dp.xml b/OsmAnd/res/layout/bottom_sheet_item_with_switch_56dp.xml new file mode 100644 index 0000000000..ad46fc49b4 --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_item_with_switch_56dp.xml @@ -0,0 +1,42 @@ + + + + + + + + + + diff --git a/OsmAnd/res/layout/bottom_sheet_item_with_switch_no_icon.xml b/OsmAnd/res/layout/bottom_sheet_item_with_switch_no_icon.xml new file mode 100644 index 0000000000..c9b104fcad --- /dev/null +++ b/OsmAnd/res/layout/bottom_sheet_item_with_switch_no_icon.xml @@ -0,0 +1,34 @@ + + + + + + + + \ No newline at end of file diff --git a/OsmAnd/res/layout/bottom_sheet_menu_base.xml b/OsmAnd/res/layout/bottom_sheet_menu_base.xml index 7e98ba5ca1..dc7ec4009f 100644 --- a/OsmAnd/res/layout/bottom_sheet_menu_base.xml +++ b/OsmAnd/res/layout/bottom_sheet_menu_base.xml @@ -36,6 +36,7 @@ android:background="?attr/dashboard_divider"/> diff --git a/OsmAnd/res/layout/coordinate_input_data_area.xml b/OsmAnd/res/layout/coordinate_input_data_area.xml index e6a4818f2d..1a92c5c83b 100644 --- a/OsmAnd/res/layout/coordinate_input_data_area.xml +++ b/OsmAnd/res/layout/coordinate_input_data_area.xml @@ -324,7 +324,7 @@ android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="@dimen/content_padding" - android:background="@color/route_info_divider_dark"/> + android:background="@color/dialog_divider_dark"/> + + + + + + + + +