在接入Google 地图到app中的时候,后期需要读取数据库中的大量坐标等字段来显示到地图上,坐标数据可能会有几百个或者几千个,在使用最简单的方式一个个添加到地图上时因数据量过多会导致加载Marker(标记点)的时长较长,内存消耗巨大,影响用户体验。
特此在这里将自己在优化步骤进行记录,已免后者踩坑
如果你是刚接触准备接入google地图的开发者(未获取到 APIKEY) 请移步:Android Google Map 开发指南(一)解决官方demo显示空白只展示google logo问题
准备工作
如果你想系统学习Google 地图接入请移步 Google地图官方中文文档
欧克,那我们就来一步步实现我们的需求吧
正式接入
通过上一篇文章,这里你应该已经可以在手机上显示Goole地图了,下面我们就正式开始接入
移动到我的位置
首先添加需要的依赖:
//接入google map 依赖
implementation 'com.google.android.gms:play-services-maps:17.0.0'
implementation 'com.google.maps.android:android-maps-utils:2.0.3'
//place sdk for android
implementation 'com.google.android.libraries.places:places:2.4.0'
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
</fragment>
这里我是按照google接入文档中选择当前位置中的部分代码实现的
来看具体实现代码:(这里因为会用到 Plcae API中的部分代码 所以建议在google map信息中心将Places API的库进行启用)
链接: google map信息中心 API库入口
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {
private GoogleMap mMap;
private boolean mLocationPermissionGranted = false;
private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1102;
private FusedLocationProviderClient mFusedLocationProviderClient;
private Location mLastKnownLocation = null;
private static final int DEFAULT_ZOOM = 4;
private LatLngBounds latlngBounds;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//拿到句柄
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
//地图可用
mapFragment.getMapAsync(this);
getLocationPermission();
}
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
LatLng sydney = new LatLng(-33.852, 151.211);
//开启google配置
updateLocationUI();
//获取设备位置信息 并将相机图像移动到我的位置
getDeviceLocation(sydney);
}
//开始location请求权限
private void getLocationPermission() {
if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
mLocationPermissionGranted = true;
} else {
ActivityCompat.requestPermissions(this,
new String[]{
android.Manifest.permission.ACCESS_FINE_LOCATION},
PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
}
}
//权限请求回调
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
mLocationPermissionGranted = false;
switch (requestCode) {
case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mLocationPermissionGranted = true;
}
}
}
updateLocationUI();
}
//用户已授予位置权限,则在地图上启用“我的位置”图层和相关控件
// 否则停用它们并将当前位置设为 null:
private void updateLocationUI() {
if (mMap == null) {
return;
}
try {
if (mLocationPermissionGranted) {
//我的位置Mark点
mMap.setMyLocationEnabled(true);
//设置我的位置图层出现
mMap.getUiSettings().setMyLocationButtonEnabled(true);
} else {
mMap.setMyLocationEnabled(false);
//mMap.getUiSettings().setMyLocationButtonEnabled(false);
mLastKnownLocation = null;
getLocationPermission();
}
} catch (SecurityException e) {
Log.e("Exception: %s", e.getMessage());
}
}
//获取设备位置信息
private void getDeviceLocation(final LatLng sydney) {
/*
* Get the best and most recent location of the device, which may be null in rare
* cases when a location is not available.
*/
try {
if (mLocationPermissionGranted) {
Task locationResult = mFusedLocationProviderClient.getLastLocation();
locationResult.addOnCompleteListener(this, new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task task) {
if (task.isSuccessful()) {
// Set the map's camera position to the current location of the device.
mLastKnownLocation = (Location) task.getResult();
//设置移动到的指定坐标和地图缩放等级(0-21)
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(mLastKnownLocation.getLatitude(),
mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
} else {
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, DEFAULT_ZOOM));
mMap.getUiSettings().setMyLocationButtonEnabled(false);
}
}
});
}
} catch(SecurityException e) {
Log.e("Exception: %s", e.getMessage());
}
}
}
ok,通过以上代码就实现了进入app时会默认移动当前设备的位置,实现步骤是我们在onCreate的时候先将我们的Google map进行初始化并请求当前设备位置信息的权限,然后在地图加载好的回调方法进行配置和获取当前位置信息,其中最主要的方法就是getDeviceLocation。
使用点聚合的方式加载大量Marker标记
如果你已经下载了链接: google-map-sample这个demo的话 你大概就可以了解到谷歌推荐的两种加载大量Marker标记的推荐使用方法,当然如果你没有下载也没有关系,跟着小薛老师的脚步我们一起实现吧 哈哈哈哈
来看效果图:
在我们加载大量的Marker标记的时候,在默认情况下会只显示当前图片上的数字标记,随着我们对于地图的手势放大,才会去加载当前范围中的所有具体的Marker点,这样就不会在刚开始加载的时候一下加载大量的Marker点,随着用户的手势一步步加载详细的Marker点,这样分布加载减少给内存的消耗
来看具体的实现方式:
//----------------------------------------------批量生成Marker点聚合
private void initMark() {
mClusterManager = new ClusterManager<>(this,mMap);
mMap.setOnCameraIdleListener(mClusterManager);
try {
readItems();
} catch (JSONException e) {
Toast.makeText(this, "Problem reading list of markers.", Toast.LENGTH_LONG).show();
}
//详细坐标的点击事件
mClusterManager.setOnClusterItemClickListener(new ClusterManager.OnClusterItemClickListener<MyItem>() {
@Override
public boolean onClusterItemClick(MyItem item) {
Toast.makeText(MainActivity.this,"item"+item.getPosition().latitude,Toast.LENGTH_SHORT).show();
return false;
}
});
}
private void readItems() throws JSONException{
InputStream inputStream = getResources().openRawResource(R.raw.radar_search);
List<MyItem> items = new MyItemReader().read(inputStream);
for (int i = 0; i < 10; i++) {
double offset = i / 60d;
for (MyItem item : items) {
LatLng position = item.getPosition();
double lat = position.latitude + offset;
double lng = position.longitude + offset;
MyItem offsetItem = new MyItem(lat, lng);
mClusterManager.addItem(offsetItem);
}
}
}
通过以上代码就可以实现该功能了,这就看你的需求了是不是需要这种相对于比较隐式的方式了,当然如果你需要更加直观的,类似于默认进入就加载完毕所有的那种效果的话,推荐你使用下面的这种方式哟!
使用Android GeoJSON 工具加载大量Marker标记
更加直观的话推荐使用: google Android 映射GeoJSON 工具
这种就比较直观,根据你从网络上读取的特定的GeoJSON 格式的数据来批量加载Marker标记点,本人比较喜欢这种方式来生成批量标注
来看代码实现:
private void initDiffMark() {
new DownloadGeoJsonFile().execute(getString(R.string.geojson_url));
}
//使用AsyncTask进行数据请求
private class DownloadGeoJsonFile extends AsyncTask<String, Void, GeoJsonLayer> {
@Override
protected GeoJsonLayer doInBackground(String... params) {
try {
// Open a stream from the URL
InputStream stream = new URL(params[0]).openStream();
String line;
StringBuilder result = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
//循环读取网址中的所有message
while ((line = reader.readLine()) != null) {
// Read and save each line of the stream
result.append(line);
}
// Close the stream
reader.close();
stream.close();
return new GeoJsonLayer(mMap, new JSONObject(result.toString()));
} catch (IOException e) {
Log.e("MainActivitya", "GeoJSON file could not be read");
} catch (JSONException e) {
Log.e("MainActivitya", "GeoJSON file could not be converted to a JSONObject");
}
return null;
}
@Override
protected void onPostExecute(GeoJsonLayer layer) {
if (layer != null) {
addGeoJsonLayerToMap(layer);
}
}
}
private void addGeoJsonLayerToMap(GeoJsonLayer layer) {
addColorsToMarkers(layer);
layer.addLayerToMap();
// Demonstrate receiving features via GeoJsonLayer clicks.
layer.setOnFeatureClickListener(new GeoJsonLayer.GeoJsonOnFeatureClickListener() {
@Override
public void onFeatureClick(Feature feature) {
//点击当前Marker点直接跳转到google地图
// Toast.makeText(MainActivity.this,
// "Feature clicked: " + feature.getProperty("title"),
// Toast.LENGTH_SHORT).show();
//这是直接开始导航 不是路线规划
Uri uri = Uri.parse("google.navigation:q=" + "31.9295" + "," + "104.2181");
Intent mapIntent = new Intent(Intent.ACTION_VIEW, uri);
mapIntent.setPackage("com.google.android.apps.maps");
startActivity(mapIntent);
}
});
}
/**
* Adds a point style to all features to change the color of the marker based on its magnitude
* property
*/
private void addColorsToMarkers(GeoJsonLayer layer) {
// Iterate over all the features stored in the layer
for (GeoJsonFeature feature : layer.getFeatures()) {
// Check if the magnitude property exists
if (feature.getProperty("mag") != null && feature.hasProperty("place")) {
double magnitude = Double.parseDouble(feature.getProperty("mag"));
// Get the icon for the feature
BitmapDescriptor pointIcon = BitmapDescriptorFactory
.defaultMarker(magnitudeToColor(magnitude));
// Create a new point style
GeoJsonPointStyle pointStyle = new GeoJsonPointStyle();
// Set options for the point style
pointStyle.setIcon(pointIcon);
pointStyle.setTitle("Magnitude of " + magnitude);
pointStyle.setSnippet("Earthquake occured " + feature.getProperty("place"));
// Assign the point style to the feature
feature.setPointStyle(pointStyle);
}
}
}
/**
* Assigns a color based on the given magnitude
* Mark颜色切换
*/
private static float magnitudeToColor(double magnitude) {
if (magnitude < 1.0) {
return BitmapDescriptorFactory.HUE_CYAN;
} else if (magnitude < 2.5) {
return BitmapDescriptorFactory.HUE_GREEN;
} else if (magnitude < 4.5) {
return BitmapDescriptorFactory.HUE_YELLOW;
} else {
return BitmapDescriptorFactory.HUE_RED;
}
}
欧克,到这里就算完结了,有什么问题和建议的话请在评论区告诉我哟,我再看到的时候会第一时间回复的 谢谢
完整的代码:
MainActivity.class
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback {
private GoogleMap mMap;
private boolean mLocationPermissionGranted = false;
private static final int PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION = 1102;
private FusedLocationProviderClient mFusedLocationProviderClient;
private Location mLastKnownLocation = null;
private static final int DEFAULT_ZOOM = 4;
private LatLngBounds latlngBounds;
private ClusterManager<MyItem> mClusterManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//拿到句柄
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
//地图可用
mapFragment.getMapAsync(this);
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this);
getLocationPermission();
}
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
//updateLocationUI();
LatLng sydney = new LatLng(-33.852, 151.211);
//getDeviceLocation(sydney);+
//开启google配置
updateLocationUI();
//获取设备位置信息 并将相机图像移动到我的位置
getDeviceLocation(sydney);
//批量生成Marker点(点聚合的方式)
//initMark();
//批量生成Marker点(GeoJSON方式 )->推荐
initDiffMark();
//我的位置图层按钮点击事件
//mMap.setOnMyLocationButtonClickListener(this);
//我的位置Mark点的点击事件
// mMap.setOnMyLocationClickListener(this);
// googleMap.addMarker(new MarkerOptions().position(sydney)
// .title("Marker in Sydney"));
// //googleMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
// //设置地图相机和地图缩放等级
// googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, 3));
}
private void initDiffMark() {
new DownloadGeoJsonFile().execute(getString(R.string.geojson_url));
}
//使用AsyncTask进行数据请求
private class DownloadGeoJsonFile extends AsyncTask<String, Void, GeoJsonLayer> {
@Override
protected GeoJsonLayer doInBackground(String... params) {
try {
// Open a stream from the URL
InputStream stream = new URL(params[0]).openStream();
String line;
StringBuilder result = new StringBuilder();
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
//循环读取网址中的所有message
while ((line = reader.readLine()) != null) {
// Read and save each line of the stream
result.append(line);
}
// Close the stream
reader.close();
stream.close();
return new GeoJsonLayer(mMap, new JSONObject(result.toString()));
} catch (IOException e) {
Log.e("MainActivitya", "GeoJSON file could not be read");
} catch (JSONException e) {
Log.e("MainActivitya", "GeoJSON file could not be converted to a JSONObject");
}
return null;
}
@Override
protected void onPostExecute(GeoJsonLayer layer) {
if (layer != null) {
addGeoJsonLayerToMap(layer);
}
}
}
private void addGeoJsonLayerToMap(GeoJsonLayer layer) {
addColorsToMarkers(layer);
layer.addLayerToMap();
// Demonstrate receiving features via GeoJsonLayer clicks.
layer.setOnFeatureClickListener(new GeoJsonLayer.GeoJsonOnFeatureClickListener() {
@Override
public void onFeatureClick(Feature feature) {
//点击当前Marker点直接跳转到google地图
// Toast.makeText(MainActivity.this,
// "Feature clicked: " + feature.getProperty("title"),
// Toast.LENGTH_SHORT).show();
//这是直接开始导航 不是路线规划
Uri uri = Uri.parse("google.navigation:q=" + "31.9295" + "," + "104.2181");
Intent mapIntent = new Intent(Intent.ACTION_VIEW, uri);
mapIntent.setPackage("com.google.android.apps.maps");
startActivity(mapIntent);
}
});
}
/**
* Adds a point style to all features to change the color of the marker based on its magnitude
* property
*/
private void addColorsToMarkers(GeoJsonLayer layer) {
// Iterate over all the features stored in the layer
for (GeoJsonFeature feature : layer.getFeatures()) {
// Check if the magnitude property exists
if (feature.getProperty("mag") != null && feature.hasProperty("place")) {
double magnitude = Double.parseDouble(feature.getProperty("mag"));
// Get the icon for the feature
BitmapDescriptor pointIcon = BitmapDescriptorFactory
.defaultMarker(magnitudeToColor(magnitude));
// Create a new point style
GeoJsonPointStyle pointStyle = new GeoJsonPointStyle();
// Set options for the point style
pointStyle.setIcon(pointIcon);
pointStyle.setTitle("Magnitude of " + magnitude);
pointStyle.setSnippet("Earthquake occured " + feature.getProperty("place"));
// Assign the point style to the feature
feature.setPointStyle(pointStyle);
}
}
}
/**
* Assigns a color based on the given magnitude
* Mark颜色切换
*/
private static float magnitudeToColor(double magnitude) {
if (magnitude < 1.0) {
return BitmapDescriptorFactory.HUE_CYAN;
} else if (magnitude < 2.5) {
return BitmapDescriptorFactory.HUE_GREEN;
} else if (magnitude < 4.5) {
return BitmapDescriptorFactory.HUE_YELLOW;
} else {
return BitmapDescriptorFactory.HUE_RED;
}
}
//----------------------------------------------批量生成Marker点聚合
private void initMark() {
mClusterManager = new ClusterManager<>(this,mMap);
mMap.setOnCameraIdleListener(mClusterManager);
try {
readItems();
} catch (JSONException e) {
Toast.makeText(this, "Problem reading list of markers.", Toast.LENGTH_LONG).show();
}
//详细坐标的点击事件
mClusterManager.setOnClusterItemClickListener(new ClusterManager.OnClusterItemClickListener<MyItem>() {
@Override
public boolean onClusterItemClick(MyItem item) {
Toast.makeText(MainActivity.this,"item"+item.getPosition().latitude,Toast.LENGTH_SHORT).show();
return false;
}
});
}
private void readItems() throws JSONException{
InputStream inputStream = getResources().openRawResource(R.raw.radar_search);
List<MyItem> items = new MyItemReader().read(inputStream);
for (int i = 0; i < 10; i++) {
double offset = i / 60d;
for (MyItem item : items) {
LatLng position = item.getPosition();
double lat = position.latitude + offset;
double lng = position.longitude + offset;
MyItem offsetItem = new MyItem(lat, lng);
mClusterManager.addItem(offsetItem);
}
}
}
//开始location请求权限
private void getLocationPermission() {
if (ContextCompat.checkSelfPermission(this.getApplicationContext(),
android.Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED) {
mLocationPermissionGranted = true;
} else {
ActivityCompat.requestPermissions(this,
new String[]{
android.Manifest.permission.ACCESS_FINE_LOCATION},
PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
}
}
//权限请求回调
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
mLocationPermissionGranted = false;
switch (requestCode) {
case PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mLocationPermissionGranted = true;
}
}
}
updateLocationUI();
}
//用户已授予位置权限,则在地图上启用“我的位置”图层和相关控件
// 否则停用它们并将当前位置设为 null:
private void updateLocationUI() {
if (mMap == null) {
return;
}
try {
if (mLocationPermissionGranted) {
//我的位置Mark点
mMap.setMyLocationEnabled(true);
//设置我的位置图层出现
mMap.getUiSettings().setMyLocationButtonEnabled(true);
} else {
mMap.setMyLocationEnabled(false);
//mMap.getUiSettings().setMyLocationButtonEnabled(false);
mLastKnownLocation = null;
getLocationPermission();
}
} catch (SecurityException e) {
Log.e("Exception: %s", e.getMessage());
}
}
//获取设备位置信息
private void getDeviceLocation(final LatLng sydney) {
/*
* Get the best and most recent location of the device, which may be null in rare
* cases when a location is not available.
*/
try {
if (mLocationPermissionGranted) {
Task locationResult = mFusedLocationProviderClient.getLastLocation();
locationResult.addOnCompleteListener(this, new OnCompleteListener() {
@Override
public void onComplete(@NonNull Task task) {
if (task.isSuccessful()) {
// Set the map's camera position to the current location of the device.
mLastKnownLocation = (Location) task.getResult();
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(
new LatLng(mLastKnownLocation.getLatitude(),
mLastKnownLocation.getLongitude()), DEFAULT_ZOOM));
} else {
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney, DEFAULT_ZOOM));
mMap.getUiSettings().setMyLocationButtonEnabled(false);
}
}
});
}
} catch(SecurityException e) {
Log.e("Exception: %s", e.getMessage());
}
}
}
MyItem.class
public class MyItem implements ClusterItem {
private LatLng mPosition;
private String mTitle;
private String mSnippet;
public MyItem(double lat, double lng) {
mPosition = new LatLng(lat, lng);
mTitle = null;
mSnippet = null;
}
public MyItem(double lat, double lng, String title, String snippet) {
mPosition = new LatLng(lat, lng);
mTitle = title;
mSnippet = snippet;
}
public void setTitle(String title) {
mTitle = title;
}
public void setSnippet(String snippet) {
mSnippet = snippet;
}
@NonNull
@Override
public LatLng getPosition() {
return mPosition;
}
@Nullable
@Override
public String getTitle() {
return mTitle;
}
@Nullable
@Override
public String getSnippet() {
return mSnippet;
}
}
MyItemReader .class
public class MyItemReader {
/*
* This matches only once in whole input,
* so Scanner.next returns whole InputStream as a String.
* http://stackoverflow.com/a/5445161/2183804
*/
private static final String REGEX_INPUT_BOUNDARY_BEGINNING = "\\A";
public List<MyItem> read(InputStream inputStream) throws JSONException {
List<MyItem> items = new ArrayList<MyItem>();
String json = new Scanner(inputStream).useDelimiter(REGEX_INPUT_BOUNDARY_BEGINNING).next();
JSONArray array = new JSONArray(json);
for (int i = 0; i < array.length(); i++) {
String title = null;
String snippet = null;
JSONObject object = array.getJSONObject(i);
double lat = object.getDouble("lat");
double lng = object.getDouble("lng");
if (!object.isNull("title")) {
title = object.getString("title");
}
if (!object.isNull("snippet")) {
snippet = object.getString("snippet");
}
items.add(new MyItem(lat, lng, title, snippet));
}
return items;
}
}
radar_search.json
[
{
"lat" : 51.5145160, "lng" : -0.1270060 },
{
"lat" : 51.5064490, "lng" : -0.1244260, "title" : "Corinthia Hotel London", "snippet": "Whitehall Pl"},
{
"lat" : 51.5097080, "lng" : -0.1200450, "title" : "Savoy Place", "snippet" : "Covent Garden"},
{
"lat" : 51.5090680, "lng" : -0.1421420, "title" : "Albemarle St", "snippet": "Mayfair"},
{
"lat" : 51.4976080, "lng" : -0.1456320, "title" : " Victoria Square", "snippet": " Belgravia" },
{
"lat" : 51.5046150, "lng" : -0.1473780},
{
"lat" : 51.5077540, "lng" : -0.1378760, "title" : "Jermyn Street", "snippet": "St. James's" },
{
"lat" : 51.5074250, "lng" : -0.1323230 , "title" : "Pall Mall", "snippet": "Westminster"},
{
"lat" : 51.5070030, "lng" : -0.125560 },
{
"lat" : 51.5061590, "lng" : -0.140280 },
{
"lat" : 51.5047420, "lng" : -0.1470490 },
{
"lat" : 51.5126760, "lng" : -0.1189760 },
{
"lat" : 51.5108480, "lng" : -0.1208480 },
{
"lat" : 51.5099460, "lng" : -0.1300150 },
{
"lat" : 51.5076580, "lng" : -0.1424490 },
{
"lat" : 51.5097160, "lng" : -0.1555350 },
{
"lat" : 51.5215190, "lng" : -0.1621160 },
{
"lat" : 51.5177960, "lng" : -0.1438760 },
{
"lat" : 51.5071840, "lng" : -0.1415940 },
{
"lat" : 51.5008150, "lng" : -0.1520910 },
{
"lat" : 51.5179170, "lng" : -0.142740 },
{
"lat" : 51.50360, "lng" : -0.14980 },
{
"lat" : 51.512620, "lng" : -0.1476950 },
{
"lat" : 51.5051890, "lng" : -0.08813600000000001 },
{
"lat" : 51.4969390, "lng" : -0.1594880 },
{
"lat" : 51.506020, "lng" : -0.1241340 },
{
"lat" : 51.5143270, "lng" : -0.1318940 },
{
"lat" : 51.5070480, "lng" : -0.1521930 },
{
"lat" : 51.5101640, "lng" : -0.1495920 },
{
"lat" : 51.5144240, "lng" : -0.1392980 },
{
"lat" : 51.4816380, "lng" : -0.1489180 },
{
"lat" : 51.519340, "lng" : -0.1209080 },
{
"lat" : 51.4982420, "lng" : -0.1435160 },
{
"lat" : 51.5104310, "lng" : -0.1267850 },
{
"lat" : 51.504340, "lng" : -0.149940 },
{
"lat" : 51.5174490, "lng" : -0.1370170 },
{
"lat" : 51.524370, "lng" : -0.1281460 },
{
"lat" : 51.5117770, "lng" : -0.1192630 },
{
"lat" : 51.5026220, "lng" : -0.1527130 },
{
"lat" : 51.500210, "lng" : -0.1798050 },
{
"lat" : 51.5293450, "lng" : -0.1260930 },
{
"lat" : 51.514070, "lng" : -0.0854980 },
{
"lat" : 51.5154290, "lng" : -0.1571420 },
{
"lat" : 51.5158980, "lng" : -0.1202010 },
{
"lat" : 51.5081370, "lng" : -0.1438340 },
{
"lat" : 51.4990650, "lng" : -0.1343110 },
{
"lat" : 51.5059290, "lng" : -0.1491020 },
{
"lat" : 51.5017470, "lng" : -0.1848540 },
{
"lat" : 51.510820, "lng" : -0.1511460 },
{
"lat" : 51.5128620, "lng" : -0.192130 },
{
"lat" : 51.49850, "lng" : -0.1583950 },
{
"lat" : 51.5094440, "lng" : -0.1362880 },
{
"lat" : 51.5239250, "lng" : -0.1249180 },
{
"lat" : 51.4930180, "lng" : -0.159680 },
{
"lat" : 51.5055380, "lng" : -0.1396880 },
{
"lat" : 51.4892760, "lng" : -0.180180 },
{
"lat" : 51.4999860, "lng" : -0.161490 },
{
"lat" : 51.5183870, "lng" : -0.1350570 },
{
"lat" : 51.5025980, "lng" : -0.1883030 },
{
"lat" : 51.4702570, "lng" : -0.1776650 },
{
"lat" : 51.5184940, "lng" : -0.1452920 },
{
"lat" : 51.4943450, "lng" : -0.1145180 },
{
"lat" : 51.5174860, "lng" : -0.1307490 },
{
"lat" : 51.47570, "lng" : -0.1819760 },
{
"lat" : 51.4917980, "lng" : -0.161740 },
{
"lat" : 51.5081480, "lng" : -0.1653260 },
{
"lat" : 51.5241310, "lng" : -0.18460 },
{
"lat" : 51.5255790, "lng" : -0.0828410 },
{
"lat" : 51.4944410, "lng" : -0.1360670 },
{
"lat" : 51.4924780, "lng" : -0.1483010 },
{
"lat" : 51.5101220, "lng" : -0.1967860 },
{
"lat" : 51.4947680, "lng" : -0.1186810 },
{
"lat" : 51.5108440, "lng" : -0.131580 },
{
"lat" : 51.4906890, "lng" : -0.1386160 },
{
"lat" : 51.4991350, "lng" : -0.1125320 },
{
"lat" : 51.5113950, "lng" : -0.1427780 },
{
"lat" : 51.4905960, "lng" : -0.1388970 },
{
"lat" : 51.4908430, "lng" : -0.1440980 },
{
"lat" : 51.4900210, "lng" : -0.1376870 },
{
"lat" : 51.5102170, "lng" : -0.1315040 },
{
"lat" : 51.4903170, "lng" : -0.1377590 },
{
"lat" : 51.5101040, "lng" : -0.1322540 },
{
"lat" : 51.5156830, "lng" : -0.1240560 },
{
"lat" : 51.5116380, "lng" : -0.1384670 },
{
"lat" : 51.4973190, "lng" : -0.156080 },
{
"lat" : 51.5180390, "lng" : -0.1497690 },
{
"lat" : 51.4930840, "lng" : -0.1443660 },
{
"lat" : 51.498970, "lng" : -0.1062350 },
{
"lat" : 51.5113380, "lng" : -0.1300990 },
{
"lat" : 51.4920160, "lng" : -0.1419380 },
{
"lat" : 51.507070, "lng" : -0.1049530 },
{
"lat" : 51.5059030, "lng" : -0.1403950 },
{
"lat" : 51.5160770, "lng" : -0.1353810 },
{
"lat" : 51.494140, "lng" : -0.1411990 },
{
"lat" : 51.5225950, "lng" : -0.1253190 },
{
"lat" : 51.4957540, "lng" : -0.1476890 },
{
"lat" : 51.5052860, "lng" : -0.150260 },
{
"lat" : 51.4966970, "lng" : -0.1122920 },
{
"lat" : 51.5201680, "lng" : -0.1256960 },
{
"lat" : 51.4929010, "lng" : -0.1572410 },
{
"lat" : 51.5019440, "lng" : -0.1562350 },
{
"lat" : 51.489530, "lng" : -0.1366590 },
{
"lat" : 51.5134410, "lng" : -0.1508490 },
{
"lat" : 51.5025440, "lng" : -0.118110 },
{
"lat" : 51.491640, "lng" : -0.1415830 },
{
"lat" : 51.5115110, "lng" : -0.1183470 },
{
"lat" : 51.4909510, "lng" : -0.1438210 },
{
"lat" : 51.5071040, "lng" : -0.1461620 },
{
"lat" : 51.5186970, "lng" : -0.1355270 },
{
"lat" : 51.5178830, "lng" : -0.1185430 },
{
"lat" : 51.492470, "lng" : -0.1455250 },
{
"lat" : 51.5015740, "lng" : -0.1624430 },
{
"lat" : 51.5135820, "lng" : -0.1086290 },
{
"lat" : 51.4920260, "lng" : -0.1418470 },
{
"lat" : 51.4907580, "lng" : -0.1445780 },
{
"lat" : 51.4913140, "lng" : -0.144820 },
{
"lat" : 51.490830, "lng" : -0.1443390 },
{
"lat" : 51.5139170, "lng" : -0.122090 },
{
"lat" : 51.4922580, "lng" : -0.1418670 },
{
"lat" : 51.5160140, "lng" : -0.1578750 },
{
"lat" : 51.5109460, "lng" : -0.076930 },
{
"lat" : 51.4930270, "lng" : -0.142960 },
{
"lat" : 51.49990, "lng" : -0.1781820 },
{
"lat" : 51.5035550, "lng" : -0.1113130 },
{
"lat" : 51.4903060, "lng" : -0.1403020 },
{
"lat" : 51.4936240, "lng" : -0.1500960 },
{
"lat" : 51.4919830, "lng" : -0.1414270 },
{
"lat" : 51.5050970, "lng" : -0.104710 },
{
"lat" : 51.4950920, "lng" : -0.1838080 },
{
"lat" : 51.5259280, "lng" : -0.1358780 },
{
"lat" : 51.5057060, "lng" : -0.1221970 },
{
"lat" : 51.4952410, "lng" : -0.1817070 },
{
"lat" : 51.4940420, "lng" : -0.1492270 },
{
"lat" : 51.490370, "lng" : -0.1458720 },
{
"lat" : 51.5077260, "lng" : -0.1471730 },
{
"lat" : 51.4651970, "lng" : -0.114940 },
{
"lat" : 51.5138860, "lng" : -0.1012070 },
{
"lat" : 51.518720, "lng" : -0.153870 },
{
"lat" : 51.4913010, "lng" : -0.1428370 },
{
"lat" : 51.5151350, "lng" : -0.1615710 },
{
"lat" : 51.5188840, "lng" : -0.1318310 },
{
"lat" : 51.5020890, "lng" : -0.186790 },
{
"lat" : 51.4907170, "lng" : -0.1386760 },
{
"lat" : 51.5182760, "lng" : -0.1582390 },
{
"lat" : 51.5195190, "lng" : -0.1430740 },
{
"lat" : 51.5241420, "lng" : -0.1378130 },
{
"lat" : 51.5077230, "lng" : -0.1279620 },
{
"lat" : 51.4938750, "lng" : -0.1497910 },
{
"lat" : 51.5237550, "lng" : -0.1403980 },
{
"lat" : 51.4933780, "lng" : -0.1500050 },
{
"lat" : 51.4901580, "lng" : -0.1387650 },
{
"lat" : 51.5159630, "lng" : -0.1718890 },
{
"lat" : 51.4908250, "lng" : -0.1450190 },
{
"lat" : 51.5264680, "lng" : -0.1353710 },
{
"lat" : 51.5173210, "lng" : -0.156650 },
{
"lat" : 51.5140620, "lng" : -0.1469230 },
{
"lat" : 51.5028270, "lng" : -0.0720070 },
{
"lat" : 51.5206650, "lng" : -0.1003340 },
{
"lat" : 51.502060, "lng" : -0.1599840 },
{
"lat" : 51.5277390, "lng" : -0.135320 },
{
"lat" : 51.5219420, "lng" : -0.1321670 },
{
"lat" : 51.5152990, "lng" : -0.1600470 },
{
"lat" : 51.4907870, "lng" : -0.1388240 },
{
"lat" : 51.4945090, "lng" : -0.174880 },
{
"lat" : 51.5219720, "lng" : -0.1322210 },
{
"lat" : 51.4900030, "lng" : -0.0977280 },
{
"lat" : 51.5222270, "lng" : -0.1425630 },
{
"lat" : 51.4977620, "lng" : -0.08125599999999999 },
{
"lat" : 51.5081250, "lng" : -0.07108100000000001 },
{
"lat" : 51.5179610, "lng" : -0.1529610 },
{
"lat" : 51.5129390, "lng" : -0.1577410 },
{
"lat" : 51.4935420, "lng" : -0.1594010 },
{
"lat" : 51.5157770, "lng" : -0.173090 },
{
"lat" : 51.4948150, "lng" : -0.1778260 },
{
"lat" : 51.4964460, "lng" : -0.1557010 },
{
"lat" : 51.4916430, "lng" : -0.1006060 },
{
"lat" : 51.5122920, "lng" : -0.1750390 },
{
"lat" : 51.5202170, "lng" : -0.1025320 },
{
"lat" : 51.5076930, "lng" : -0.1413620 },
{
"lat" : 51.491520, "lng" : -0.1426860 },
{
"lat" : 51.5138990, "lng" : -0.1787940 },
{
"lat" : 51.5221330, "lng" : -0.1325740 },
{
"lat" : 51.5135120, "lng" : -0.1631850 },
{
"lat" : 51.5067690, "lng" : -0.0740210 },
{
"lat" : 51.5201120, "lng" : -0.1238560 },
{
"lat" : 51.4925250, "lng" : -0.1451160 },
{
"lat" : 51.5162150, "lng" : -0.1733980 },
{
"lat" : 51.5228320, "lng" : -0.155350 },
{
"lat" : 51.5212420, "lng" : -0.1558640 },
{
"lat" : 51.5284530, "lng" : -0.1226970 },
{
"lat" : 51.5223030, "lng" : -0.1325830 },
{
"lat" : 51.5197430, "lng" : -0.1431740 },
{
"lat" : 51.5216440, "lng" : -0.1318630 },
{
"lat" : 51.5172640, "lng" : -0.08090 },
{
"lat" : 51.474050, "lng" : -0.0895920 },
{
"lat" : 51.5181490, "lng" : -0.1581730 },
{
"lat" : 51.5159250, "lng" : -0.1600310 },
{
"lat" : 51.5243350, "lng" : -0.1165120 },
{
"lat" : 51.4907560, "lng" : -0.1434930 },
{
"lat" : 51.4900980, "lng" : -0.1373290 }
]
最后在这里祝各位1024节日快乐!
祝各位永无BUG