feat(geo): auto-derive geometry из data.lat/lon + backfill migration
Записи ground_station и similar dicts имеют lat/lon в data, но geometryWkt не задан. Spatial filter (AOI) WHERE geometry IS NOT NULL AND ST_Intersects(...) возвращал 0 records даже когда координаты есть. Три места починены: 1. DictionaryRecordService.resolveGeometry — для CRUD через REST. Если req.geometryWkt() задан — приоритет (explicit). Иначе читаем data.lat/lon (или latitude/longitude/lng), валидируем ranges, строим POINT(lon lat) с SRID 4326. Игнор out-of-range без throw — не ломаем legacy data. 2. CuodBundleImporter.deriveLatLonGeometry — bundle bypass'ит service, reuse'аем ту же логику локально (5 строк inline). Применяется при создании НОВЫХ records (existing skipped по businessKey). 3. Liquibase migration 0015-backfill-latlon-geometry — UPDATE existing records где geometry IS NULL AND data has lat/lon. Три changeset'а для разных aliases (lat/lon, latitude/longitude, lat/lng). Idempotent (только NULL→value), rollback supported (NULL обратно). После deploy: existing 3 ground_station записей (Отрадное и др.) и spacecraft с lat/lon получат POINT geometry. AOI bbox-фильтр заработает.
This commit is contained in:
+35
@@ -207,6 +207,11 @@ public class CuodBundleImporter {
|
||||
}
|
||||
DictionaryRecord record = new DictionaryRecord(
|
||||
UUID.randomUUID(), definition.getId(), businessKey, data, definition.getScope(), now);
|
||||
// Auto-derive POINT geometry из data.lat/lon — match service behaviour.
|
||||
// Без этого AOI spatial filter возвращает 0 records даже когда у dict
|
||||
// есть записи с координатами.
|
||||
org.locationtech.jts.geom.Geometry derived = deriveLatLonGeometry(data);
|
||||
if (derived != null) record.setGeometry(derived);
|
||||
DictionaryRecord savedRecord = recordRepository.save(record);
|
||||
|
||||
var recEvent = new DictionaryRecordCreated(
|
||||
@@ -228,4 +233,34 @@ public class CuodBundleImporter {
|
||||
private static cloud.nstart.terravault.ordinis.events.DataScope toEventScope(DataScope d) {
|
||||
return cloud.nstart.terravault.ordinis.events.DataScope.valueOf(d.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Auto-derive POINT(lon lat) geometry из data.lat/lon. Mirror'ит logic в
|
||||
* DictionaryRecordService.resolveGeometry — bundle importer bypass'ит
|
||||
* service, поэтому ту же конвенцию реализуем тут отдельно. Match keys:
|
||||
* lat|latitude, lon|longitude|lng. Validate ranges.
|
||||
*/
|
||||
private static org.locationtech.jts.geom.Geometry deriveLatLonGeometry(JsonNode data) {
|
||||
if (data == null || data.isMissingNode()) return null;
|
||||
Double lat = pickNumber(data, "lat", "latitude");
|
||||
Double lon = pickNumber(data, "lon", "longitude", "lng");
|
||||
if (lat == null || lon == null) return null;
|
||||
if (lat < -90.0 || lat > 90.0 || lon < -180.0 || lon > 180.0) return null;
|
||||
try {
|
||||
org.locationtech.jts.geom.Geometry g = new org.locationtech.jts.io.WKTReader()
|
||||
.read("POINT(" + lon + " " + lat + ")");
|
||||
g.setSRID(4326);
|
||||
return g;
|
||||
} catch (org.locationtech.jts.io.ParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Double pickNumber(JsonNode data, String... keys) {
|
||||
for (String k : keys) {
|
||||
JsonNode n = data.get(k);
|
||||
if (n != null && n.isNumber()) return n.doubleValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user