[{"data":1,"prerenderedAt":1302},["ShallowReactive",2],{"article-\u002Farticles\u002Felasticsearch-geo-map-search":3},{"id":4,"title":5,"body":6,"date":1288,"description":32,"excerpt":1289,"extension":1290,"meta":1291,"navigation":1292,"path":1293,"seo":1294,"stem":1295,"tags":1296,"__hash__":1301},"articles\u002Farticles\u002Felasticsearch-geo-map-search.md","用 Elasticsearch 做地圖找房：geo_point、geo_bounding_box 與 geo_distance",{"type":7,"value":8,"toc":1279},"minimark",[9,13,26,142,150,163,167,173,297,306,385,402,412,416,423,438,670,673,705,709,715,944,969,973,979,996,1006,1194,1208,1212,1215,1244,1247,1250,1275],[10,11,12],"h2",{"id":12},"為什麼地圖搜尋不適合用關聯式資料庫做",[14,15,16,17,21,22,25],"p",{},"如果物件表上有 ",[18,19,20],"code",{},"Lat","、",[18,23,24],{},"Lon"," 兩個欄位，「方框內的物件」可以寫成：",[27,28,33],"pre",{"className":29,"code":30,"language":31,"meta":32,"style":32},"language-sql shiki shiki-themes github-dark","SELECT * FROM Houses\nWHERE Lat BETWEEN 25.030 AND 25.048\n  AND Lon BETWEEN 121.550 AND 121.580\n  AND Price \u003C= 3000 AND Rooms >= 3;\n","sql","",[18,34,35,54,86,114],{"__ignoreMap":32},[36,37,40,44,47,50],"span",{"class":38,"line":39},"line",1,[36,41,43],{"class":42},"snl16","SELECT",[36,45,46],{"class":42}," *",[36,48,49],{"class":42}," FROM",[36,51,53],{"class":52},"s95oV"," Houses\n",[36,55,57,60,63,66,70,73,76,79,81,83],{"class":38,"line":56},2,[36,58,59],{"class":42},"WHERE",[36,61,62],{"class":52}," Lat ",[36,64,65],{"class":42},"BETWEEN",[36,67,69],{"class":68},"sDLfK"," 25",[36,71,72],{"class":52},".",[36,74,75],{"class":68},"030",[36,77,78],{"class":42}," AND",[36,80,69],{"class":68},[36,82,72],{"class":52},[36,84,85],{"class":68},"048\n",[36,87,89,92,95,97,100,102,105,107,109,111],{"class":38,"line":88},3,[36,90,91],{"class":42},"  AND",[36,93,94],{"class":52}," Lon ",[36,96,65],{"class":42},[36,98,99],{"class":68}," 121",[36,101,72],{"class":52},[36,103,104],{"class":68},"550",[36,106,78],{"class":42},[36,108,99],{"class":68},[36,110,72],{"class":52},[36,112,113],{"class":68},"580\n",[36,115,117,119,122,125,128,130,133,136,139],{"class":38,"line":116},4,[36,118,91],{"class":42},[36,120,121],{"class":52}," Price ",[36,123,124],{"class":42},"\u003C=",[36,126,127],{"class":68}," 3000",[36,129,78],{"class":42},[36,131,132],{"class":52}," Rooms ",[36,134,135],{"class":42},">=",[36,137,138],{"class":68}," 3",[36,140,141],{"class":52},";\n",[14,143,144,145,149],{},"資料量小的時候沒什麼問題，但地圖搜尋的特性是：",[146,147,148],"strong",{},"使用者每拖曳一次、縮放一次，就要重新查詢一次","，還要疊上總價、房數、屋齡等篩選條件。兩個欄位的範圍條件很難同時有效利用一般的 B-Tree 索引；「某個點方圓一公里內」更需要計算距離，每一筆都算一次就是全表掃描。",[14,151,152,153,156,157,162],{},"Elasticsearch 的 ",[18,154,155],{},"geo_point"," 欄位專門為這種查詢設計，地理條件可以和其他篩選條件一起在索引層完成。基本的索引、Mapping 與 Query DSL 概念可以參考 ",[158,159,161],"a",{"href":160},"\u002Farticles\u002Felasticsearch-getting-started","Elasticsearch 入門"," 這篇。",[10,164,166],{"id":165},"geo_point先把座標存對","geo_point：先把座標存對",[14,168,169,170,172],{},"Mapping 中把座標欄位宣告為 ",[18,171,155],{},"：",[27,174,178],{"className":175,"code":176,"language":177,"meta":32,"style":32},"language-json shiki shiki-themes github-dark","PUT \u002Fhouses\n{\n  \"mappings\": {\n    \"properties\": {\n      \"title\":    { \"type\": \"text\" },\n      \"price\":    { \"type\": \"integer\" },\n      \"rooms\":    { \"type\": \"integer\" },\n      \"location\": { \"type\": \"geo_point\" }\n    }\n  }\n}\n","json",[18,179,180,185,190,198,205,227,244,260,279,285,291],{"__ignoreMap":32},[36,181,182],{"class":38,"line":39},[36,183,184],{"class":52},"PUT \u002Fhouses\n",[36,186,187],{"class":38,"line":56},[36,188,189],{"class":52},"{\n",[36,191,192,195],{"class":38,"line":88},[36,193,194],{"class":68},"  \"mappings\"",[36,196,197],{"class":52},": {\n",[36,199,200,203],{"class":38,"line":116},[36,201,202],{"class":68},"    \"properties\"",[36,204,197],{"class":52},[36,206,208,211,214,217,220,224],{"class":38,"line":207},5,[36,209,210],{"class":68},"      \"title\"",[36,212,213],{"class":52},":    { ",[36,215,216],{"class":68},"\"type\"",[36,218,219],{"class":52},": ",[36,221,223],{"class":222},"sU2Wk","\"text\"",[36,225,226],{"class":52}," },\n",[36,228,230,233,235,237,239,242],{"class":38,"line":229},6,[36,231,232],{"class":68},"      \"price\"",[36,234,213],{"class":52},[36,236,216],{"class":68},[36,238,219],{"class":52},[36,240,241],{"class":222},"\"integer\"",[36,243,226],{"class":52},[36,245,247,250,252,254,256,258],{"class":38,"line":246},7,[36,248,249],{"class":68},"      \"rooms\"",[36,251,213],{"class":52},[36,253,216],{"class":68},[36,255,219],{"class":52},[36,257,241],{"class":222},[36,259,226],{"class":52},[36,261,263,266,269,271,273,276],{"class":38,"line":262},8,[36,264,265],{"class":68},"      \"location\"",[36,267,268],{"class":52},": { ",[36,270,216],{"class":68},[36,272,219],{"class":52},[36,274,275],{"class":222},"\"geo_point\"",[36,277,278],{"class":52}," }\n",[36,280,282],{"class":38,"line":281},9,[36,283,284],{"class":52},"    }\n",[36,286,288],{"class":38,"line":287},10,[36,289,290],{"class":52},"  }\n",[36,292,294],{"class":38,"line":293},11,[36,295,296],{"class":52},"}\n",[14,298,299,300,302,303,172],{},"寫入時，",[18,301,155],{}," 支援好幾種格式，",[146,304,305],{},"最容易出錯的是順序",[27,307,309],{"className":175,"code":308,"language":177,"meta":32,"style":32},"{ \"location\": { \"lat\": 25.0330, \"lon\": 121.5654 } }   \u002F\u002F 物件格式：清楚，推薦\n{ \"location\": \"25.0330,121.5654\" }                    \u002F\u002F 字串格式：lat,lon\n{ \"location\": [121.5654, 25.0330] }                   \u002F\u002F 陣列格式：lon, lat（順序相反！）\n",[18,310,311,347,364],{"__ignoreMap":32},[36,312,313,316,319,321,324,326,329,332,335,337,340,343],{"class":38,"line":39},[36,314,315],{"class":52},"{ ",[36,317,318],{"class":68},"\"location\"",[36,320,268],{"class":52},[36,322,323],{"class":68},"\"lat\"",[36,325,219],{"class":52},[36,327,328],{"class":68},"25.0330",[36,330,331],{"class":52},", ",[36,333,334],{"class":68},"\"lon\"",[36,336,219],{"class":52},[36,338,339],{"class":68},"121.5654",[36,341,342],{"class":52}," } }   ",[36,344,346],{"class":345},"sAwPA","\u002F\u002F 物件格式：清楚，推薦\n",[36,348,349,351,353,355,358,361],{"class":38,"line":56},[36,350,315],{"class":52},[36,352,318],{"class":68},[36,354,219],{"class":52},[36,356,357],{"class":222},"\"25.0330,121.5654\"",[36,359,360],{"class":52}," }                    ",[36,362,363],{"class":345},"\u002F\u002F 字串格式：lat,lon\n",[36,365,366,368,370,373,375,377,379,382],{"class":38,"line":88},[36,367,315],{"class":52},[36,369,318],{"class":68},[36,371,372],{"class":52},": [",[36,374,339],{"class":68},[36,376,331],{"class":52},[36,378,328],{"class":68},[36,380,381],{"class":52},"] }                   ",[36,383,384],{"class":345},"\u002F\u002F 陣列格式：lon, lat（順序相反！）\n",[14,386,387,388,393,394,397,398,401],{},"陣列格式遵循 GeoJSON 的慣例，是 ",[146,389,390],{},[18,391,392],{},"[經度, 緯度]","，和字串格式剛好相反。如果以為陣列也是「緯度在前」而寫成 ",[18,395,396],{},"[25.0330, 121.5654]","，緯度就變成 121.5654——緯度只能介於 −90 到 90，這筆資料會直接寫入失敗；更麻煩的是兩個值剛好都在合法範圍內的情況，資料會寫入成功，但物件默默被放到地球的另一個地方。",[146,399,400],{},"統一使用物件格式","，可以完全避開這個問題。",[403,404,405],"blockquote",{},[14,406,407,408,411],{},"Mapping 一旦建立，欄位型別就不能修改。如果座標已經用 ",[18,409,410],{},"float"," 存進去了，必須建新索引、重新寫入資料，所以第一次建立索引時就要決定好。",[10,413,415],{"id":414},"geo_bounding_box地圖可視範圍內的物件","geo_bounding_box：地圖可視範圍內的物件",[14,417,418],{},[419,420],"img",{"alt":421,"src":422},"兩種地理查詢示意圖：左邊 geo_bounding_box 以地圖可視範圍的左上角與右下角座標畫出方框，只回傳框內的物件，框外的物件不回傳；右邊 geo_distance 以目前物件為圓心、半徑 1 公里畫圓，圓內的物件依距離由近到遠排序，圓外的物件排除","\u002Fimages\u002Farticles\u002Fes-geo-queries.svg",[14,424,425,426,429,430,433,434,437],{},"地圖元件（Google Maps、Leaflet 等）都可以在拖曳、縮放結束後取得目前畫面的邊界。把",[146,427,428],{},"左上角","與",[146,431,432],{},"右下角","的座標送到後端，組成 ",[18,435,436],{},"geo_bounding_box"," 查詢：",[27,439,441],{"className":175,"code":440,"language":177,"meta":32,"style":32},"GET \u002Fhouses\u002F_search\n{\n  \"size\": 200,\n  \"_source\": [\"id\", \"price\", \"location\"],\n  \"query\": {\n    \"bool\": {\n      \"filter\": [\n        {\n          \"geo_bounding_box\": {\n            \"location\": {\n              \"top_left\":     { \"lat\": 25.0480, \"lon\": 121.5500 },\n              \"bottom_right\": { \"lat\": 25.0300, \"lon\": 121.5800 }\n            }\n          }\n        },\n        { \"range\": { \"price\": { \"lte\": 3000 } } },\n        { \"range\": { \"rooms\": { \"gte\": 3 } } }\n      ]\n    }\n  }\n}\n",[18,442,443,448,452,465,487,494,501,509,514,521,528,554,580,586,592,598,624,649,655,660,665],{"__ignoreMap":32},[36,444,445],{"class":38,"line":39},[36,446,447],{"class":52},"GET \u002Fhouses\u002F_search\n",[36,449,450],{"class":38,"line":56},[36,451,189],{"class":52},[36,453,454,457,459,462],{"class":38,"line":88},[36,455,456],{"class":68},"  \"size\"",[36,458,219],{"class":52},[36,460,461],{"class":68},"200",[36,463,464],{"class":52},",\n",[36,466,467,470,472,475,477,480,482,484],{"class":38,"line":116},[36,468,469],{"class":68},"  \"_source\"",[36,471,372],{"class":52},[36,473,474],{"class":222},"\"id\"",[36,476,331],{"class":52},[36,478,479],{"class":222},"\"price\"",[36,481,331],{"class":52},[36,483,318],{"class":222},[36,485,486],{"class":52},"],\n",[36,488,489,492],{"class":38,"line":207},[36,490,491],{"class":68},"  \"query\"",[36,493,197],{"class":52},[36,495,496,499],{"class":38,"line":229},[36,497,498],{"class":68},"    \"bool\"",[36,500,197],{"class":52},[36,502,503,506],{"class":38,"line":246},[36,504,505],{"class":68},"      \"filter\"",[36,507,508],{"class":52},": [\n",[36,510,511],{"class":38,"line":262},[36,512,513],{"class":52},"        {\n",[36,515,516,519],{"class":38,"line":281},[36,517,518],{"class":68},"          \"geo_bounding_box\"",[36,520,197],{"class":52},[36,522,523,526],{"class":38,"line":287},[36,524,525],{"class":68},"            \"location\"",[36,527,197],{"class":52},[36,529,530,533,536,538,540,543,545,547,549,552],{"class":38,"line":293},[36,531,532],{"class":68},"              \"top_left\"",[36,534,535],{"class":52},":     { ",[36,537,323],{"class":68},[36,539,219],{"class":52},[36,541,542],{"class":68},"25.0480",[36,544,331],{"class":52},[36,546,334],{"class":68},[36,548,219],{"class":52},[36,550,551],{"class":68},"121.5500",[36,553,226],{"class":52},[36,555,557,560,562,564,566,569,571,573,575,578],{"class":38,"line":556},12,[36,558,559],{"class":68},"              \"bottom_right\"",[36,561,268],{"class":52},[36,563,323],{"class":68},[36,565,219],{"class":52},[36,567,568],{"class":68},"25.0300",[36,570,331],{"class":52},[36,572,334],{"class":68},[36,574,219],{"class":52},[36,576,577],{"class":68},"121.5800",[36,579,278],{"class":52},[36,581,583],{"class":38,"line":582},13,[36,584,585],{"class":52},"            }\n",[36,587,589],{"class":38,"line":588},14,[36,590,591],{"class":52},"          }\n",[36,593,595],{"class":38,"line":594},15,[36,596,597],{"class":52},"        },\n",[36,599,601,604,607,609,611,613,616,618,621],{"class":38,"line":600},16,[36,602,603],{"class":52},"        { ",[36,605,606],{"class":68},"\"range\"",[36,608,268],{"class":52},[36,610,479],{"class":68},[36,612,268],{"class":52},[36,614,615],{"class":68},"\"lte\"",[36,617,219],{"class":52},[36,619,620],{"class":68},"3000",[36,622,623],{"class":52}," } } },\n",[36,625,627,629,631,633,636,638,641,643,646],{"class":38,"line":626},17,[36,628,603],{"class":52},[36,630,606],{"class":68},[36,632,268],{"class":52},[36,634,635],{"class":68},"\"rooms\"",[36,637,268],{"class":52},[36,639,640],{"class":68},"\"gte\"",[36,642,219],{"class":52},[36,644,645],{"class":68},"3",[36,647,648],{"class":52}," } } }\n",[36,650,652],{"class":38,"line":651},18,[36,653,654],{"class":52},"      ]\n",[36,656,658],{"class":38,"line":657},19,[36,659,284],{"class":52},[36,661,663],{"class":38,"line":662},20,[36,664,290],{"class":52},[36,666,668],{"class":38,"line":667},21,[36,669,296],{"class":52},[14,671,672],{},"幾個重點：",[674,675,676,689,699],"ul",{},[677,678,679,685,686,688],"li",{},[146,680,681,682],{},"所有條件都放在 ",[18,683,684],{},"filter","：地理範圍與篩選條件都是「符合／不符合」，不需要計算相關性分數。放在 ",[18,687,684],{}," 中，Elasticsearch 會跳過評分，並且可以快取篩選結果",[677,690,691,694,695,698],{},[146,692,693],{},"只回傳地圖需要的欄位","：地圖上的價格標籤只需要 ID、價格和座標，用 ",[18,696,697],{},"_source"," 過濾掉標題、描述、照片等大欄位，回應大小可以小好幾倍。使用者點了某個標籤，再查那一筆的詳細資料",[677,700,701,704],{},[146,702,703],{},"限制回傳數量","：使用者把地圖縮小到整個台北市時，框內可能有上萬筆物件。一次全部丟給前端，傳輸慢、地圖也畫不動（下面會談怎麼處理）",[10,706,708],{"id":707},"geo_distance附近物件推薦","geo_distance：附近物件推薦",[14,710,711,712,172],{},"物件詳細頁的「附近物件」，是以目前物件為圓心，找出一定半徑內的其他物件，再",[146,713,714],{},"依距離排序",[27,716,718],{"className":175,"code":717,"language":177,"meta":32,"style":32},"GET \u002Fhouses\u002F_search\n{\n  \"size\": 6,\n  \"query\": {\n    \"bool\": {\n      \"filter\": [\n        {\n          \"geo_distance\": {\n            \"distance\": \"1km\",\n            \"location\": { \"lat\": 25.0330, \"lon\": 121.5654 }\n          }\n        }\n      ],\n      \"must_not\": [\n        { \"term\": { \"id\": \"H-102938\" } }\n      ]\n    }\n  },\n  \"sort\": [\n    {\n      \"_geo_distance\": {\n        \"location\": { \"lat\": 25.0330, \"lon\": 121.5654 },\n        \"order\": \"asc\",\n        \"unit\": \"m\"\n      }\n    }\n  ]\n}\n",[18,719,720,724,728,739,745,751,757,761,768,780,802,806,811,816,823,842,846,850,855,862,867,874,898,911,922,928,933,939],{"__ignoreMap":32},[36,721,722],{"class":38,"line":39},[36,723,447],{"class":52},[36,725,726],{"class":38,"line":56},[36,727,189],{"class":52},[36,729,730,732,734,737],{"class":38,"line":88},[36,731,456],{"class":68},[36,733,219],{"class":52},[36,735,736],{"class":68},"6",[36,738,464],{"class":52},[36,740,741,743],{"class":38,"line":116},[36,742,491],{"class":68},[36,744,197],{"class":52},[36,746,747,749],{"class":38,"line":207},[36,748,498],{"class":68},[36,750,197],{"class":52},[36,752,753,755],{"class":38,"line":229},[36,754,505],{"class":68},[36,756,508],{"class":52},[36,758,759],{"class":38,"line":246},[36,760,513],{"class":52},[36,762,763,766],{"class":38,"line":262},[36,764,765],{"class":68},"          \"geo_distance\"",[36,767,197],{"class":52},[36,769,770,773,775,778],{"class":38,"line":281},[36,771,772],{"class":68},"            \"distance\"",[36,774,219],{"class":52},[36,776,777],{"class":222},"\"1km\"",[36,779,464],{"class":52},[36,781,782,784,786,788,790,792,794,796,798,800],{"class":38,"line":287},[36,783,525],{"class":68},[36,785,268],{"class":52},[36,787,323],{"class":68},[36,789,219],{"class":52},[36,791,328],{"class":68},[36,793,331],{"class":52},[36,795,334],{"class":68},[36,797,219],{"class":52},[36,799,339],{"class":68},[36,801,278],{"class":52},[36,803,804],{"class":38,"line":293},[36,805,591],{"class":52},[36,807,808],{"class":38,"line":556},[36,809,810],{"class":52},"        }\n",[36,812,813],{"class":38,"line":582},[36,814,815],{"class":52},"      ],\n",[36,817,818,821],{"class":38,"line":588},[36,819,820],{"class":68},"      \"must_not\"",[36,822,508],{"class":52},[36,824,825,827,830,832,834,836,839],{"class":38,"line":594},[36,826,603],{"class":52},[36,828,829],{"class":68},"\"term\"",[36,831,268],{"class":52},[36,833,474],{"class":68},[36,835,219],{"class":52},[36,837,838],{"class":222},"\"H-102938\"",[36,840,841],{"class":52}," } }\n",[36,843,844],{"class":38,"line":600},[36,845,654],{"class":52},[36,847,848],{"class":38,"line":626},[36,849,284],{"class":52},[36,851,852],{"class":38,"line":651},[36,853,854],{"class":52},"  },\n",[36,856,857,860],{"class":38,"line":657},[36,858,859],{"class":68},"  \"sort\"",[36,861,508],{"class":52},[36,863,864],{"class":38,"line":662},[36,865,866],{"class":52},"    {\n",[36,868,869,872],{"class":38,"line":667},[36,870,871],{"class":68},"      \"_geo_distance\"",[36,873,197],{"class":52},[36,875,877,880,882,884,886,888,890,892,894,896],{"class":38,"line":876},22,[36,878,879],{"class":68},"        \"location\"",[36,881,268],{"class":52},[36,883,323],{"class":68},[36,885,219],{"class":52},[36,887,328],{"class":68},[36,889,331],{"class":52},[36,891,334],{"class":68},[36,893,219],{"class":52},[36,895,339],{"class":68},[36,897,226],{"class":52},[36,899,901,904,906,909],{"class":38,"line":900},23,[36,902,903],{"class":68},"        \"order\"",[36,905,219],{"class":52},[36,907,908],{"class":222},"\"asc\"",[36,910,464],{"class":52},[36,912,914,917,919],{"class":38,"line":913},24,[36,915,916],{"class":68},"        \"unit\"",[36,918,219],{"class":52},[36,920,921],{"class":222},"\"m\"\n",[36,923,925],{"class":38,"line":924},25,[36,926,927],{"class":52},"      }\n",[36,929,931],{"class":38,"line":930},26,[36,932,284],{"class":52},[36,934,936],{"class":38,"line":935},27,[36,937,938],{"class":52},"  ]\n",[36,940,942],{"class":38,"line":941},28,[36,943,296],{"class":52},[674,945,946,952,966],{},[677,947,948,951],{},[18,949,950],{},"must_not"," 排除目前這一筆物件自己，否則距離 0 公尺的它永遠排第一",[677,953,954,957,958,961,962,965],{},[18,955,956],{},"_geo_distance"," 排序時，每一筆結果的 ",[18,959,960],{},"sort"," 欄位會帶回",[146,963,964],{},"實際距離","，前端可以直接顯示「距離 320 公尺」，不需要自己再算一次",[677,967,968],{},"半徑內的物件太少時（例如郊區），可以在後端判斷筆數不足就放大半徑再查一次，避免推薦區塊空空的",[10,970,972],{"id":971},"縮小到整個城市時用聚合代替點","縮小到整個城市時：用聚合代替點",[14,974,975,976,172],{},"地圖縮得越小，框內的物件越多。這時候在地圖上畫幾千個標籤，不但看不清楚，瀏覽器也會很吃力。常見的做法是",[146,977,978],{},"依縮放層級切換顯示方式",[674,980,981,990],{},[677,982,983,986,987,989],{},[146,984,985],{},"放大（街道層級）","：用 ",[18,988,436],{}," 回傳個別物件，顯示價格標籤",[677,991,992,995],{},[146,993,994],{},"縮小（行政區、城市層級）","：改用聚合，回傳「每一塊區域有幾筆物件」，在地圖上顯示數量泡泡",[14,997,998,1001,1002,1005],{},[18,999,1000],{},"geotile_grid"," 聚合會依地圖圖磚（Tile）切分區域，",[18,1003,1004],{},"precision"," 對應地圖的縮放層級：",[27,1007,1009],{"className":175,"code":1008,"language":177,"meta":32,"style":32},"GET \u002Fhouses\u002F_search\n{\n  \"size\": 0,\n  \"query\": {\n    \"geo_bounding_box\": {\n      \"location\": {\n        \"top_left\":     { \"lat\": 25.21, \"lon\": 121.45 },\n        \"bottom_right\": { \"lat\": 24.96, \"lon\": 121.67 }\n      }\n    }\n  },\n  \"aggs\": {\n    \"grid\": {\n      \"geotile_grid\": { \"field\": \"location\", \"precision\": 13 },\n      \"aggs\": {\n        \"center\": { \"geo_centroid\": { \"field\": \"location\" } }\n      }\n    }\n  }\n}\n",[18,1010,1011,1015,1019,1030,1036,1043,1049,1074,1099,1103,1107,1111,1118,1125,1151,1158,1178,1182,1186,1190],{"__ignoreMap":32},[36,1012,1013],{"class":38,"line":39},[36,1014,447],{"class":52},[36,1016,1017],{"class":38,"line":56},[36,1018,189],{"class":52},[36,1020,1021,1023,1025,1028],{"class":38,"line":88},[36,1022,456],{"class":68},[36,1024,219],{"class":52},[36,1026,1027],{"class":68},"0",[36,1029,464],{"class":52},[36,1031,1032,1034],{"class":38,"line":116},[36,1033,491],{"class":68},[36,1035,197],{"class":52},[36,1037,1038,1041],{"class":38,"line":207},[36,1039,1040],{"class":68},"    \"geo_bounding_box\"",[36,1042,197],{"class":52},[36,1044,1045,1047],{"class":38,"line":229},[36,1046,265],{"class":68},[36,1048,197],{"class":52},[36,1050,1051,1054,1056,1058,1060,1063,1065,1067,1069,1072],{"class":38,"line":246},[36,1052,1053],{"class":68},"        \"top_left\"",[36,1055,535],{"class":52},[36,1057,323],{"class":68},[36,1059,219],{"class":52},[36,1061,1062],{"class":68},"25.21",[36,1064,331],{"class":52},[36,1066,334],{"class":68},[36,1068,219],{"class":52},[36,1070,1071],{"class":68},"121.45",[36,1073,226],{"class":52},[36,1075,1076,1079,1081,1083,1085,1088,1090,1092,1094,1097],{"class":38,"line":262},[36,1077,1078],{"class":68},"        \"bottom_right\"",[36,1080,268],{"class":52},[36,1082,323],{"class":68},[36,1084,219],{"class":52},[36,1086,1087],{"class":68},"24.96",[36,1089,331],{"class":52},[36,1091,334],{"class":68},[36,1093,219],{"class":52},[36,1095,1096],{"class":68},"121.67",[36,1098,278],{"class":52},[36,1100,1101],{"class":38,"line":281},[36,1102,927],{"class":52},[36,1104,1105],{"class":38,"line":287},[36,1106,284],{"class":52},[36,1108,1109],{"class":38,"line":293},[36,1110,854],{"class":52},[36,1112,1113,1116],{"class":38,"line":556},[36,1114,1115],{"class":68},"  \"aggs\"",[36,1117,197],{"class":52},[36,1119,1120,1123],{"class":38,"line":582},[36,1121,1122],{"class":68},"    \"grid\"",[36,1124,197],{"class":52},[36,1126,1127,1130,1132,1135,1137,1139,1141,1144,1146,1149],{"class":38,"line":588},[36,1128,1129],{"class":68},"      \"geotile_grid\"",[36,1131,268],{"class":52},[36,1133,1134],{"class":68},"\"field\"",[36,1136,219],{"class":52},[36,1138,318],{"class":222},[36,1140,331],{"class":52},[36,1142,1143],{"class":68},"\"precision\"",[36,1145,219],{"class":52},[36,1147,1148],{"class":68},"13",[36,1150,226],{"class":52},[36,1152,1153,1156],{"class":38,"line":594},[36,1154,1155],{"class":68},"      \"aggs\"",[36,1157,197],{"class":52},[36,1159,1160,1163,1165,1168,1170,1172,1174,1176],{"class":38,"line":600},[36,1161,1162],{"class":68},"        \"center\"",[36,1164,268],{"class":52},[36,1166,1167],{"class":68},"\"geo_centroid\"",[36,1169,268],{"class":52},[36,1171,1134],{"class":68},[36,1173,219],{"class":52},[36,1175,318],{"class":222},[36,1177,841],{"class":52},[36,1179,1180],{"class":38,"line":626},[36,1181,927],{"class":52},[36,1183,1184],{"class":38,"line":651},[36,1185,284],{"class":52},[36,1187,1188],{"class":38,"line":657},[36,1189,290],{"class":52},[36,1191,1192],{"class":38,"line":662},[36,1193,296],{"class":52},[14,1195,1196,1199,1200,1203,1204,1207],{},[18,1197,1198],{},"geo_centroid"," 計算每一格內物件的中心點，把數量泡泡畫在物件實際集中的位置，而不是格子的正中央。另一種做法是依「行政區」這類 ",[18,1201,1202],{},"keyword"," 欄位做 ",[18,1205,1206],{},"terms"," 聚合，把泡泡畫在行政區的中心，對使用者來說更直覺。",[10,1209,1211],{"id":1210},"使用情境房仲平台的地圖找房","使用情境：房仲平台的地圖找房",[14,1213,1214],{},"在房仲平台的買屋與成交行情頻道中，地圖找房的流程是：",[1216,1217,1218,1225,1231,1234],"ol",{},[677,1219,1220,1221,1224],{},"使用者拖曳或縮放地圖，前端在動作",[146,1222,1223],{},"結束後","（加上 300ms 左右的 Debounce，避免拖曳過程中連續發出請求）送出目前的邊界座標與篩選條件",[677,1226,1227,1228,1230],{},"中台依縮放層級決定查詢方式：街道層級用 ",[18,1229,436],{}," 回傳物件；較大範圍改回傳聚合結果",[677,1232,1233],{},"回應只帶 ID、價格、座標，前端畫出價格標籤；點擊標籤時再查詢物件詳細資料",[677,1235,1236,1237,1240,1241,1243],{},"物件詳細頁以 ",[18,1238,1239],{},"geo_distance"," 加上 ",[18,1242,956],{}," 排序，推薦附近 1 公里內的物件",[14,1245,1246],{},"成交行情頻道用的是同一套做法，差別只在索引的內容是實價登錄的成交紀錄，標籤上顯示的是成交單價。",[10,1248,1249],{"id":1249},"一句話總結",[14,1251,1252,1253,1255,1256,1259,1260,1262,1263,1265,1266,1268,1269,1271,1272,1274],{},"座標用 ",[18,1254,155],{}," 存、而且統一用 ",[18,1257,1258],{},"{ lat, lon }"," 物件格式避免經緯度寫反；地圖可視範圍用 ",[18,1261,436],{},"、附近推薦用 ",[18,1264,1239],{}," 搭配 ",[18,1267,956],{}," 排序，條件全部放在 ",[18,1270,684],{}," 並只回傳必要欄位；地圖縮得很小時，改用 ",[18,1273,1000],{}," 聚合回傳數量，而不是把上萬個點丟給前端。",[1276,1277,1278],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}",{"title":32,"searchDepth":56,"depth":56,"links":1280},[1281,1282,1283,1284,1285,1286,1287],{"id":12,"depth":56,"text":12},{"id":165,"depth":56,"text":166},{"id":414,"depth":56,"text":415},{"id":707,"depth":56,"text":708},{"id":971,"depth":56,"text":972},{"id":1210,"depth":56,"text":1211},{"id":1249,"depth":56,"text":1249},"2023-05-30",null,"md",{},true,"\u002Farticles\u002Felasticsearch-geo-map-search",{"title":5,"description":32},"articles\u002Felasticsearch-geo-map-search",[1297,1298,1299,1300],"Elasticsearch","GEO","地圖搜尋","搜尋引擎","9gOrT8PmPx8ukhOYza4liQg1M2ii7L1tdj6IW_Tfudk",1792959150536]