ElasticSearch – GeoIP – hledani dle statu/oblasti

Posledni dobou vyuzivam hodne ChatGPT jako junior programatora po ruce pro scripty ktery jsem liny psat … nicmene se hodi i jako docela dobry konzultant pro veci, ktere jsou slozitejsi a clovek si neni jisty jak je resit, nebo ze jdou vubec resit. Dneska jsem diky nemu sprovoznil vyhledavani v datech (obrazky s geo souradnicema) dle zeme kde byl obrazek porizen.
Vedel jsem, ze Elastic ma typ objektu geo_point na ktery lze pak aplikovat hledani typu: Najdi me obrazky ktere jsou v 1km vzdalenosti od lokality LAT/LON. Tak jsem si rikal, jestli vlastne jde nejak hledat i v oblasti konkretni zeme. Zjistil jsem, ze ES podporuje tkzv geo_shape, tedy vydefinovane oblasti souradnicema, nicmene tyto oblasti si musim najit sam.
Pri hledani dal jsem zjistil, ze tyto souradnice jsou k dispozici na internetu z mnoha zdroju s ruznym vyberem presnosti a jsou ve formatu GeoJSON. Zdroj ktery jsem pouzil ja byl nakonec: https://github.com/simonepri/geo-maps/blob/master/info/countries-land.md
Jenze co s tim ? Preci nebudu ukladat souradnice do nejaky externi databaze abych pak vyplnoval dlouhe zadani to ES dotazu s geo_shape.
Diky ChatGPT jsem ale zjistil, ze ES podporuje nacitani Geo dat z jineho indexu, nez ve kterem hledam! Bingo!
Rozhodl jsem si tedy vytvorit index geoips s mappingem:
{ "mappings": { "properties": { "coordinates": { "type": "geo_shape" }, "country": { "type": "keyword" } } } }
Bohuzel pro prilis striktni validaci ze strany geo_shape se mi nepodarilo naimportovat spravne vsechny GeoJSON jednotlivych zemi sveta a proto jsem se nakonec rozhodl upravit mapping tak aby odpovidal tomu co ES chce, ale zaroven me nebuzeroval s problemy:
{ "mappings": { "properties": { "coordinates": { "properties": { "coordinates": { "type": "float" } } }, "country": { "type": "keyword" } } } }
Abych mohl GeoJSON do ES nahrat, potrebuju upravit trochu format puvodniho JSON a to aby obsahoval „country“ a „coordinates“ a nic vic, to jsem docilil pres python script od ChatGPT takto:
import json # Načtení GeoJSON souboru with open('Downloads/countries-land-10km.geo.json', 'r') as file: geojson = json.load(file) # Vytvoření a uložení nového souboru with open('country_geoip.json', 'w') as file: for feature in geojson['features']: country = feature['properties']['A3'] # předpokládáme, že A3 je kód země geometry = feature['geometry'] # Vytvoření nového formátu new_format = {"country": country, "coordinates": geometry} # Převedení nového formátu na json string a zapsání do souboru file.write(json.dumps(new_format) + '\n')
A cilovy country_geoip.json jsem naimportoval do ES pres esbulk :
esbulk -skipbroken -index geoips -id country -server localhost:9200 -verbose country_geoip.json
A jak nakonec takove vyhledavani v ES podle Geo vypada ? Napriklad takto, kdy si vybiram vsechny dokumenty s geo lokaci v Singapuru 🙂
{ "query": { "bool": { "must": { "match_all": {} }, "filter": { "geo_shape": { "location": { "indexed_shape": { "index": "geoips", "id": "SGS", "path": "coordinates" } } } } } } }
Kody jednotlivych statu lze najit napriklad na strankach Ministerstva Vnitra nebo v JSON formatu na Githubu. Urcite jsem mohl nahradit ID za 2-letter code nebo za nazev zeme rovnou v importu, ale to pro muj ucel nebylo nutne. Nicmene, zde je upraveny Python script, ktery nam z ISO-Alpha-3 udela ISO-Alpha-2:
import json # Načtení GeoJSON souboru with open('./Downloads/countries-land-10km.geo.json', 'r') as file: geojson = json.load(file) # Načtení souboru s definicí zemí with open('countries.json', 'r') as file: countries = json.load(file) # Vytvoření slovníku pro rychlé vyhledávání alpha-2 kódů podle alpha-3 kódů alpha2_dict = {country['alpha-3']: country['alpha-2'] for country in countries} # Vytvoření a uložení nového souboru with open('geoips.json', 'w') as file: for feature in geojson['features']: country_alpha3 = feature['properties']['A3'] # ISO_A3 je kód země geometry = feature['geometry'] # Pokud existuje příslušný alpha-2 kód, nahradíme jím alpha-3 kód country_alpha2 = alpha2_dict.get(country_alpha3, country_alpha3) # Vytvoření nového formátu new_format = {"country": country_alpha2, "coordinates": geometry} # Převedení nového formátu na json string a zapsání do souboru file.write(json.dumps(new_format) + '\n')