經緯度轉郵遞區號(免費) Lat Lng to Zip

前言

圖資的費用相當的昂貴,台灣大部分可靠的圖資來源

就我所知有Google、Map8

Map8也是有一些額外需要處理判斷的

一般情況如果只需要Zip還需要透過圖資的話,是相當的浪費成本,可以大大節省費用

事先準備資料

取得資料

取得台灣各縣市的經緯度資料

我國各鄉(鎮、市、區)行政區域界線圖資

https://data.gov.tw/dataset/7441

下載的資料檔案轉出可以使用的json檔

.dbf, .prj, .shp, .shx 檔案一同轉出geojson

https://products.aspose.app/gis/conversion/shapefile-to-json

整理資料

將各縣市區域的最大範圍先存起來,方便之後快速篩選符合的名單

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ini_set('memory_limit','2048M');
$file = Storage::get('TOWN_MOI_1100415.geojson');
$datas = json_decode($file, true);
$infos = [];
foreach ($datas['features'] as $index => $data) {
$infos[] = [
'property' => $data['properties'],
'maxRange' => $this->getMaxRange($data['geometry']['coordinates']),
];
$path = 'area/' . $data['properties']['COUNTYCODE'] . '/' . $data['properties']['TOWNCODE']. '.json';

if (!is_float($data['geometry']['coordinates'][0][0][0])) {
foreach ($data['geometry']['coordinates'] as $index2 => $area) {
$data['geometry']['coordinates'][$index2] = $data['geometry']['coordinates'][$index2][0];
}
}

Storage::disk('public')->put($path, json_encode($data['geometry']['coordinates']));
};

Storage::disk('public')->put('area/max_range.json', json_encode($infos));

實作方法

利用射線法來判斷「點」是否在多邊形內,

穿出範圍要是經過為「奇數」為內部

方法與邏輯(減少判斷Loading)

  1. 取得各行政區的最大與最小經緯度 max_lat, max_lng, min_lat, min_lng,將符合的區域列入候補名單中
  2. 假設查詢的點剛好在線的「起始」、「終點」其中一點上,則判斷為圈內
  3. 假設查詢的點(Y)與該線的(Y)一樣,判斷為擦邊不算經過的點之一
  4. 判斷查詢的點(Y)是否在比對的線的(Y)範圍內
  5. 計算左射線與比對線是否有香蕉 (count+1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
foreach ($qualifierAreas as $area) {
$filePath = 'area/' . $area['property']['COUNTYCODE'] . '/' . $area['property']['TOWNCODE'] . '.json';
$blocks = json_decode(Storage::disk('public')->get($filePath), true);
Log::info('符合名單', [$area['property']]);
//一個行政區有可能多個圈(ex:澎湖)
foreach ($blocks as $block) {
$startPoint = $block[0];
$count = 0;
$inside = true;
for ($i = 1 ; $i < count($block) ; $i++) {
$endPoint = $block[$i];
// 在點頂上
if (($point[0] == $startPoint[0] && $point[1] == $startPoint[1])
|| ($point[0] == $endPoint[0] && $point[1] == $endPoint[1])) {
$inside = true;
}

// 查詢的點(Y)與該線的(Y)一樣,判斷為擦邊不算經過的點之一
if ($point[1] == min($startPoint[1], $endPoint[1])) {
$startPoint = $endPoint;

continue;
}

// 判斷水平射線是否Y在兩點範圍內
$maxY = max($startPoint[1], $endPoint[1]);
$minY = min($startPoint[1], $endPoint[1]);
if ($point[1] <= $minY || $point[1] >= $maxY) {
$startPoint = $endPoint;

continue;
}

//計算左射線與線兩條是否相交
$x = $endPoint[0] - ($endPoint[1] - $point[1]) * ($endPoint[0] - $startPoint[0]) / ($endPoint[1] - $startPoint[1]);

if ($x < $point[0]) {
Log::info('第' . ($count+1) .'個香蕉點:', [$startPoint, $endPoint, [$x, $point[1]]]);
$count += 1;
} else if ($x > $point[0]) {
Log::info('香蕉點在右側:', [$startPoint, $endPoint, [$x, $point[1]]]);
} else {
Log::info('點在多邊形上');
$inside = false;
}

$startPoint = $endPoint;
}

if ($count % 2 == 0 || !$inside) {
Log::info('在多邊形外');
continue;
}

Log::info('在多邊形內------------------------', [$area]);
$costTime = microtime(true) - $startTime;
Log::info('結束總耗費' . $costTime);

return $area;
}
}

DEMO網址

https://tool.tyeydy.com/area-by-lat-lng

參考網站:

http://wyj-learning.blogspot.com/2018/07/python-100.html
https://www.itread01.com/content/1546013522.html

如果這一篇文章有幫助到你的話,
請幫忙點選廣告,都是友善方式不強迫點擊