From c2aecdf93f969c897c88edc462a6c17f237332ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lkd9125=28=EC=9D=B4=EA=B2=BD=EB=8F=84=29?= Date: Wed, 21 Feb 2024 18:50:26 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Flt=EB=AA=A8=EB=93=88=20-=20=EA=B3=B5?= =?UTF-8?q?=EC=97=AD=EC=B2=B4=ED=81=AC,=20=ED=97=88=EC=9A=A9=EA=B3=A0?= =?UTF-8?q?=EB=8F=84=20=EC=A1=B0=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 8 +- .../kac/data/com/domain/ComConfirmBas.java | 2 +- web/api-flight/build.gradle | 22 +- .../co/palnet/kac/api/util/AirspaceUtils.java | 284 +++++++++++++++ .../kr/co/palnet/kac/api/util/AreaUtils.java | 344 ++++++++++++++++++ .../util/model/FlightPlanAreaCoordModel.java | 22 ++ .../controller/FlightLaancController.java | 26 ++ .../valid/LaancAllowableElevationRS.java | 11 + .../model/valid/LaancAreaByAirspaceModel.java | 21 ++ .../model/valid/LaancAreaByElevModel.java | 19 + .../model/valid/LaancAreaCoordModel.java | 14 + .../valid/LaancDuplicatedAirspaceRs.java | 15 + .../laanc/service/FlightLaancService.java | 91 ++++- 13 files changed, 869 insertions(+), 10 deletions(-) create mode 100644 web/api-flight/src/main/java/kr/co/palnet/kac/api/util/AirspaceUtils.java create mode 100644 web/api-flight/src/main/java/kr/co/palnet/kac/api/util/AreaUtils.java create mode 100644 web/api-flight/src/main/java/kr/co/palnet/kac/api/util/model/FlightPlanAreaCoordModel.java create mode 100644 web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAllowableElevationRS.java create mode 100644 web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaByAirspaceModel.java create mode 100644 web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaByElevModel.java create mode 100644 web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaCoordModel.java create mode 100644 web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancDuplicatedAirspaceRs.java diff --git a/build.gradle b/build.gradle index e8bebf4..fcc30ca 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,13 @@ allprojects { plugins.apply("idea") repositories { - mavenCentral() + mavenCentral { + content { + excludeModule("javax.media", "jai_core") + } + } + maven { url "https://repo.osgeo.org/repository/release" } + } configurations { diff --git a/data/com/src/main/java/kr/co/palnet/kac/data/com/domain/ComConfirmBas.java b/data/com/src/main/java/kr/co/palnet/kac/data/com/domain/ComConfirmBas.java index 45a6dbf..6eb5fc8 100644 --- a/data/com/src/main/java/kr/co/palnet/kac/data/com/domain/ComConfirmBas.java +++ b/data/com/src/main/java/kr/co/palnet/kac/data/com/domain/ComConfirmBas.java @@ -34,7 +34,7 @@ public class ComConfirmBas { // 상태 @Column(name = "STATUS", length = 20, nullable = false) - private String status; // `GENERATED`, `RECEIVED`, CHECKED, FAILED, EXPIRED + private String status; // GENERATED, RECEIVED, CHECKED, FAILED, EXPIRED // 대상구분 @Column(name = "TARGET_TYPE", length = 100) diff --git a/web/api-flight/build.gradle b/web/api-flight/build.gradle index f3561b8..c643e3b 100644 --- a/web/api-flight/build.gradle +++ b/web/api-flight/build.gradle @@ -1,14 +1,32 @@ - dependencies { implementation "$boot:spring-boot-starter-web" implementation "$boot:spring-boot-starter-data-jpa" + implementation 'javax.media:jai_core:1.1.3' + + // thymeleaf implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + // pdf create + implementation 'com.itextpdf:html2pdf:5.0.3' + + // other implementation 'org.apache.httpcomponents.client5:httpclient5:5.3' implementation 'org.apache.commons:commons-io:1.3.2' - implementation 'com.itextpdf:html2pdf:5.0.3' + implementation 'com.googlecode.json-simple:json-simple:1.1.1' + + // geometry + implementation 'com.esri.geometry:esri-geometry-api:2.2.4' + implementation 'org.locationtech.proj4j:proj4j:1.3.0' + implementation 'org.locationtech.proj4j:proj4j-epsg:1.3.0' + implementation 'org.locationtech.jts:jts-core:1.19.0' + implementation 'org.geotools:gt-geojson:29.2' + implementation 'org.geotools:gt-coverage:29.2' + implementation 'org.geotools:gt-geotiff:29.2' + implementation 'org.geotools:gt-epsg-hsql:29.2' + + // QR Code implementation 'com.google.zxing:core:3.5.2' implementation 'com.google.zxing:javase:3.5.2' diff --git a/web/api-flight/src/main/java/kr/co/palnet/kac/api/util/AirspaceUtils.java b/web/api-flight/src/main/java/kr/co/palnet/kac/api/util/AirspaceUtils.java new file mode 100644 index 0000000..52f51cf --- /dev/null +++ b/web/api-flight/src/main/java/kr/co/palnet/kac/api/util/AirspaceUtils.java @@ -0,0 +1,284 @@ +package kr.co.palnet.kac.api.util; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.geotools.geojson.feature.FeatureJSON; +import org.geotools.geojson.geom.GeometryJSON; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; +import org.opengis.feature.simple.SimpleFeature; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +public class AirspaceUtils { + + private final String CLASS_PATH = "air/elev2d"; + private final GeometryFactory geometryFactory = new GeometryFactory(); + private List airspaces; + + + private AirspaceUtils() { + // 초기화 + log.info("===== AirspaceUtils init ====="); + this.loadResourceAirspace(); + } + + public static AirspaceUtils getInstance() { + return LazyHolder.INSTANCE; + } + + private static class LazyHolder { + private static final AirspaceUtils INSTANCE = new AirspaceUtils(); + } + + // 공영 중복 검사 - 고도x + public boolean isDuplicatedAirspace(FeatureInfo target) { + Geometry targetGeometry = target.getGeometry(); + return this.airspaces.stream().anyMatch(featureInfo -> { + Geometry featureGeometry = featureInfo.getGeometry(); + return featureGeometry.intersects(targetGeometry); + }); + } + + // 공역 중복 검사 + public boolean isDuplicatedAirspaceElev(FeatureInfo target) { + if (this.airspaces == null || this.airspaces.isEmpty()) return true; + Integer targetHighElev = target.getHighElev(); + if (targetHighElev == null) targetHighElev = 0; + Geometry targetGeometry = target.getGeometry(); + + for (FeatureInfo airspace : this.airspaces) { + Integer airspaceHighElev = airspace.getHighElev(); + Geometry airspaceGeometry = airspace.getGeometry(); + + // 임시로 0~최대고도 기준으로 검증 + if (airspaceHighElev <= targetHighElev) { + if (airspaceGeometry.intersects(targetGeometry)) { + return true; + } + } + } + return false; + } + + /* + LAANC 공역 검사 + - 비행구역이 공역(금지구역)에 접근할 경우만 검사 - 통과 true, 실패 false + - 겹치지 않을 경우는 실패로 처리 - false + */ + public boolean isValidLaancAirspace(FeatureInfo target) { + if (this.airspaces == null || this.airspaces.isEmpty()) return true; + final int targetHighElev = target.getHighElev() != null ? target.getHighElev() : 0; + Geometry targetGeometry = target.getGeometry(); + + boolean isDuplicated = this.airspaces.stream().anyMatch(featureInfo -> { + Geometry featureGeometry = featureInfo.getGeometry(); + return featureGeometry.intersects(targetGeometry); + }); + // 공역(금지구역)과 겹치지 않을 경우는 실패로 처리 + if (!isDuplicated) return false; + + // 겹칠 경우 공역과 비교 + for (FeatureInfo featureInfo : this.airspaces) { + Geometry airspaceGeometry = featureInfo.getGeometry(); + int airspaceHighElev = featureInfo.getHighElev() != null ? featureInfo.getHighElev() : 0; + // 0~최대고도 기준으로 검증 + if (airspaceHighElev == 0 || airspaceHighElev < targetHighElev) { + if (airspaceGeometry.intersects(targetGeometry)) { + return false; + } + } + } + return true; + } + + // get allowable elevation + public Integer getAllowElevation(Geometry target) { + Integer allowElevation = null; + if (this.airspaces == null || this.airspaces.isEmpty()) return null; + // target과 겹치는 airspace 조회 + List duplicationAirspace = this.airspaces.stream().filter(featureInfo -> { + Geometry featureGeometry = featureInfo.getGeometry(); + return featureGeometry.intersects(target); + }).collect(Collectors.toList()); + // 중복된 airspace가 없을 경우 기본 허용고도 반환 150m(관제권x) + if (duplicationAirspace.isEmpty()) return 150; + allowElevation = duplicationAirspace.stream().map(FeatureInfo::getHighElev).min(Integer::compareTo).orElse(150); + return allowElevation; + } + + // convert coordiate to geometry + public Geometry createGeometryByCoordinate(List target) { + return this.createGeometryByCoordinate(target, "Polygon"); + } + + // convert coordiate to geometry + public Geometry createGeometryByCoordinate(List target, String type) { + Geometry geometry = null; + if ("Polygon".equals(type)) { + if (target == null || target.isEmpty()) return null; + log.info(">>> {}", target.get(0) != target.get(target.size() - 1)); + if (target.get(0) != target.get(target.size() - 1)) { + target.add(target.get(0)); + } + geometry = this.geometryFactory.createPolygon(target.toArray(new Coordinate[0])); + } else if ("LineString".equals(type)) { + geometry = this.geometryFactory.createLineString(target.toArray(new Coordinate[0])); + } else if ("Point".equals(type)) { + geometry = this.geometryFactory.createPoint(target.get(0)); + } + return geometry; + } + + // get geometry + private List getAirspaceGeometry() { + return this.airspaces.stream().map(FeatureInfo::getGeometry).collect(Collectors.toList()); + } + + // 파일에서 공역 데이터 가져와서 geometry로 변환 - 초기화. + private void loadResourceAirspace() { + + List featureInfos = new ArrayList<>(); + ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource[] resources = null; + try { + resources = resolver.getResources("classpath:" + CLASS_PATH + "/*-elev.json"); + } catch (IOException e) { + log.warn("airspaces load error : {}", e.getMessage()); + } + + if (resources == null) { + log.info("airspace resources is null"); + return; + } + + for (Resource resource : resources) { + try (InputStream is = resource.getInputStream()) { + BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + JSONParser jsonParser = new JSONParser(); + JSONObject jsonObject = (JSONObject) jsonParser.parse(reader); + List fis = this.convertGeoJsonToGeometry(jsonObject); + featureInfos.addAll(fis); + } catch (Exception e) { + log.warn("airspace resource read error : {}", e.getMessage()); + } + } + + log.info(">>> featureInfos size : {}", featureInfos.size()); + + this.airspaces = featureInfos; + } + + private List convertGeoJsonToGeometry(JSONObject jsonObject) { + List featureInfos = new ArrayList<>(); + String type = (String) jsonObject.get("type"); + if ("FeatureCollection".equals(type)) { + List features = (List) jsonObject.get("features"); +// log.debug(">>> features size : {}", features.size()); + for (JSONObject feature : features) { + JSONObject geometryObject = (JSONObject) feature.get("geometry"); + String geometryType = String.valueOf(geometryObject.get("type")); + + List coordinatesObject = (List) geometryObject.get("coordinates"); + if ("Polygon".equals(geometryType)) { + List innerObject = (List) coordinatesObject.get(0); + JSONArray firstCoords = innerObject.get(0); + JSONArray lastCoords = innerObject.get(innerObject.size() - 1); + BigDecimal ff = new BigDecimal(String.valueOf(firstCoords.get(0))); + BigDecimal fl = new BigDecimal(String.valueOf(firstCoords.get(1))); + BigDecimal lf = new BigDecimal(String.valueOf(lastCoords.get(0))); + BigDecimal ll = new BigDecimal(String.valueOf(lastCoords.get(1))); + if (!ff.equals(lf) || !fl.equals(ll)) { + JSONObject propertiesObject = (JSONObject) feature.get("properties"); +// String nameObject = String.valueOf(propertiesObject.get("name")); +// String descriptionObject = String.valueOf(propertiesObject.get("description")); +// log.info("coords first and last coords not eqauls : name/descriion = {}/{}", nameObject, descriptionObject); + innerObject.add(firstCoords); + } + } + + try { + FeatureJSON featureJSON = new FeatureJSON(); + SimpleFeature simpleFeature = null; + simpleFeature = featureJSON.readFeature(feature.toJSONString()); + Boolean use = Boolean.valueOf(String.valueOf(simpleFeature.getAttribute("use"))); + if (use) { + String name = String.valueOf(simpleFeature.getAttribute("name")); + String description = String.valueOf(simpleFeature.getAttribute("description")); + Integer lowElev = Integer.parseInt(String.valueOf(simpleFeature.getAttribute("lowElev"))); + Integer highElev = Integer.parseInt(String.valueOf(simpleFeature.getAttribute("highElev"))); + Geometry geometry = (Geometry) simpleFeature.getDefaultGeometry(); +// log.debug(">>> name, description, use, lowElev, highElev : {}, {}, {}, {}, {}", name, description, use, lowElev, highElev); + FeatureInfo info = new FeatureInfo(name, description, lowElev, highElev, geometry); + featureInfos.add(info); + } + } catch (IOException e) { + log.error("geometry json read error : {}", e.getMessage()); + } + } + } else if ("Feature".equals(type)) { + FeatureJSON featureJSON = new FeatureJSON(); + try { + SimpleFeature simpleFeature = featureJSON.readFeature(jsonObject.toJSONString()); + Boolean use = Boolean.valueOf(String.valueOf(simpleFeature.getAttribute("use"))); + if (use) { + String name = String.valueOf(simpleFeature.getAttribute("name")); + String description = String.valueOf(simpleFeature.getAttribute("description")); + Integer lowElev = Integer.parseInt(String.valueOf(simpleFeature.getAttribute("lowElev"))); + Integer highElev = Integer.parseInt(String.valueOf(simpleFeature.getAttribute("highElev"))); + Geometry geometry = (Geometry) simpleFeature.getDefaultGeometry(); + FeatureInfo info = new FeatureInfo(name, description, lowElev, highElev, geometry); + featureInfos.add(info); + } + } catch (IOException e) { + log.error("geometry json read error : {}", e.getMessage()); + } + + } else { + GeometryJSON geoJson = new GeometryJSON(); + try { + Geometry geometry = geoJson.read(jsonObject.toJSONString()); + FeatureInfo info = new FeatureInfo(null, null, null, null, geometry); + } catch (IOException e) { + log.error("geometry json read error : {}", e.getMessage()); + } + } + return featureInfos; + } + + public int getSize() { + if(this.airspaces == null) return 0; + return this.airspaces.size(); + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class FeatureInfo { + private String name; + private String description; + private Integer lowElev; + private Integer highElev; + private Geometry geometry; + } + + +} diff --git a/web/api-flight/src/main/java/kr/co/palnet/kac/api/util/AreaUtils.java b/web/api-flight/src/main/java/kr/co/palnet/kac/api/util/AreaUtils.java new file mode 100644 index 0000000..114336a --- /dev/null +++ b/web/api-flight/src/main/java/kr/co/palnet/kac/api/util/AreaUtils.java @@ -0,0 +1,344 @@ +package kr.co.palnet.kac.api.util; + +import kr.co.palnet.kac.api.util.model.FlightPlanAreaCoordModel; +import lombok.extern.slf4j.Slf4j; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.locationtech.jts.geom.*; +import org.locationtech.jts.operation.buffer.BufferOp; +import org.locationtech.jts.operation.buffer.BufferParameters; +import org.locationtech.jts.util.GeometricShapeFactory; +import org.locationtech.proj4j.BasicCoordinateTransform; +import org.locationtech.proj4j.CRSFactory; +import org.locationtech.proj4j.CoordinateReferenceSystem; +import org.locationtech.proj4j.ProjCoordinate; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Component; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +@Component +@Slf4j +public class AreaUtils { + + private List polygons = new ArrayList<>(); + private List points = new ArrayList<>(); + + private GeometryFactory geometryFactory = new GeometryFactory(); + + public AreaUtils() throws Exception { + this.init(); + } + + /** + * TODO 좌표계 변환 + * + * @param coordList + * @return + */ + @Cacheable(value = "coordTransform") + public List transform(List coordList, String from, String to) { + CRSFactory factory = new CRSFactory(); + + CoordinateReferenceSystem srcCRS = factory.createFromName(from); + CoordinateReferenceSystem tgtCRS = factory.createFromName(to); + + BasicCoordinateTransform transform = new BasicCoordinateTransform(srcCRS, tgtCRS); + + List result = new ArrayList<>(); + + for(Coordinate coord : coordList) { + ProjCoordinate projCoordinate = new ProjCoordinate(coord.getX(), coord.getY()); + ProjCoordinate projTrasform = transform.transform(projCoordinate, new ProjCoordinate()); + + Coordinate target = new Coordinate(projTrasform.x, projTrasform.y); + + result.add(target); + } + + return result; + } + + /** + * TODO 비행 구역 생성시 장애물 영역 포함 체크 + * + * @param targetCoordList - 비행구역 좌표 리스트 + * @return boolean - true(비정상), false(정상) + */ + public boolean overlaps(List targetCoordList) { + boolean result = false; + targetCoordList.add(targetCoordList.get(0)); + Polygon targetPolygon = geometryFactory.createPolygon(targetCoordList.toArray(new Coordinate[] {})); + + /* 공역 데이터 */ + if(polygons.size() > 0) { + for(Polygon polygon : polygons) { + Geometry overlabsGeometry = geometryFactory.createGeometry(polygon); + Geometry targetGeometry = geometryFactory.createGeometry(targetPolygon); + + boolean over = targetPolygon.overlaps(polygon); + + if(over) { + result = true; + break; + } + } + } + + return result; + } + + /** + * TODO 구역 포함 여부 + * + * @param targetCoordList - 생성 구역 + * @param effectiveCoordList - 유효한 비행 구역 + * @return boolean - true(비정상), false(정상) + */ + public boolean overlaps(List targetCoordList, List effectiveCoordList) { + targetCoordList.add(targetCoordList.get(0)); + Polygon targetPolygon = geometryFactory.createPolygon(targetCoordList.toArray(new Coordinate[] {})); + + effectiveCoordList.add(effectiveCoordList.get(0)); + Polygon effectivePolygon = geometryFactory.createPolygon(effectiveCoordList.toArray(new Coordinate[] {})); + + return targetPolygon.overlaps(effectivePolygon); + } + + /** + * TODO 드론 관제시 정상 비행 체크 (공역) + * + * @param targetCoordinate - 드론 좌표 정보 + * @return boolean - true(비정상), false(정상) + */ + public boolean contains(Coordinate targetCoordinate) { + boolean result = true; + + Point target = geometryFactory.createPoint(targetCoordinate); + + for(Polygon polygon : polygons) { + boolean contains = polygon.contains(target); + + if(contains) { + result = true; + break; + } + } + + return result; + } + + /** + * TODO 드론 관제시 정상 비행 체크 (비행 구역) + * + * @param areaCoordList - 비행 구역 좌표 리스트 + * @param targetCoordinate - 드론 좌표 정보 + * @return boolean - true(비정상), false(정상) + */ + public boolean contains(List areaCoordList, Coordinate targetCoordinate) { + boolean result = true; + areaCoordList.add(areaCoordList.get(0)); + if(targetCoordinate != null) { + Polygon plan = geometryFactory.createPolygon(areaCoordList.toArray(new Coordinate[]{})); // 비행 구역 + Point target = geometryFactory.createPoint(targetCoordinate); + + result = plan.contains(target); +// target.contains(plan); + } + + return result; + } + + /** + * TODO 비행 구역 - Buffer 영역 생성 + * + * @param coordList - 비행 구역 기초 좌표 리스트 + * @param bufferZone - 반경 + * + */ + public List buffer(List coordList, double bufferZone) { + List bufferList = new ArrayList<>(); + + LineString line = geometryFactory.createLineString(coordList.toArray(new Coordinate[] {})); + Geometry geometry = geometryFactory.createGeometry(line); + + // buffer + int nSegments = 10; + int cap = BufferParameters.CAP_SQUARE; + int join = BufferParameters.JOIN_ROUND; + + BufferParameters bufferParam = new BufferParameters(nSegments, cap, join, join); + BufferOp ops = new BufferOp(geometry, bufferParam); + +// Geometry bufTrans = ops.getResultGeometry((bufferZone/177763.63662580872)*2); + Geometry bufTrans = ops.getResultGeometry(bufferZone); + + Coordinate[] coords = bufTrans.getCoordinates(); + + bufferList.addAll(Arrays.asList(coords)); + + return bufferList; + } + + /** + * TODO 타원 생성 + * + * @param CircleCoord + * @param BufferZone + * @return + */ + public List createCircle(Coordinate CircleCoord, double BufferZone) { + GeometricShapeFactory shapeFactory = new GeometricShapeFactory(); + + double lng = CircleCoord.x; + double lat = CircleCoord.y; + double diameterInMeters = BufferZone; + + shapeFactory.setCentre(new Coordinate(lng , lat)); + shapeFactory.setHeight((diameterInMeters * 2) / 111320d); + shapeFactory.setWidth((diameterInMeters * 2) / (40075000 * Math.cos(Math.toRadians(lat)) / 360)); + shapeFactory.setNumPoints(64); + + final Polygon circle = shapeFactory.createCircle(); + circle.setSRID(4326); + + Geometry geometry = geometryFactory.createGeometry(circle); + Coordinate[] coords = geometry.getCoordinates(); + List coordList = new ArrayList<>(); + coordList.addAll(Arrays.asList(coords)); + + return coordList; + } + + /** + * TODO service model -> jts model mapping + * + * @param coordList + * @return + */ + public List convertCoordinates(List coordList) { + List coordinates = new ArrayList<>(); + + for(FlightPlanAreaCoordModel coord : coordList) { + Coordinate coordinate = new Coordinate(coord.getLon(), coord.getLat()); + + coordinates.add(coordinate); + } + + return coordinates; + } + + /** + * TODO jts model -> service model mapiing + * + * @param bufferList + * @return + */ + public List convertModel(List bufferList) { + List bufferCoordList = new ArrayList<>(); + + for(Coordinate buffer : bufferList) { + FlightPlanAreaCoordModel bufferCoord = new FlightPlanAreaCoordModel(); + + bufferCoord.setLat(buffer.getY()); + bufferCoord.setLon(buffer.getX()); + + bufferCoordList.add(bufferCoord); + } + + return bufferCoordList; + } + + // TODO 공역 데이터 생성 + public void init() throws Exception { + // 1. file read + ClassPathResource resource = new ClassPathResource("air/airgeo.json"); + InputStream jsonInputStream = resource.getInputStream(); + InputStreamReader inputStreamReader = new InputStreamReader(jsonInputStream, "UTF-8"); + BufferedReader reader = new BufferedReader(inputStreamReader); + + // 2. json parsing + JSONParser jsonParser = new JSONParser(); + JSONObject jsonObject = (JSONObject) jsonParser.parse(reader); + + List features = (List) jsonObject.get("features"); + + for(int i=0; i coordinates = (List) geometry.get("coordinates"); + String type = (String) geometry.get("type"); + + if("Polygon".equals(type)) { + for(JSONArray coordList : coordinates) { + List polygonPaths = new ArrayList<>(); + + for(Object coord : coordList) { + Object y = ((JSONArray) coord).get(0); + Object x = ((JSONArray) coord).get(1); + + Double lon = y instanceof Double ? (Double) y : Double.valueOf((Long) y); + Double lat = x instanceof Double ? (Double) x : Double.valueOf((Long) x); + + Coordinate areaCoord = new Coordinate(lon, lat); + + polygonPaths.add(areaCoord); + } + + Polygon polygon = geometryFactory.createPolygon(polygonPaths.toArray(new Coordinate[] {})); + + polygons.add(polygon); + } + } + + if("LineString".equals(type)) { + List lineStringPaths = new ArrayList<>(); + for(JSONArray coordList : coordinates) { + + Object y = coordList.get(0); + Object x = coordList.get(1); + + Double lon = y instanceof Double ? (Double) y : Double.valueOf((Long) y); + Double lat = x instanceof Double ? (Double) x : Double.valueOf((Long) x); + + Coordinate areaCoord = new Coordinate(lon, lat); + + lineStringPaths.add(areaCoord); + } + + Polygon polygon = geometryFactory.createPolygon(lineStringPaths.toArray(new Coordinate[] {})); + + polygons.add(polygon); + } + + if("Point".equals(type)) { + Object y = coordinates.get(0); + Object x = coordinates.get(1); + + Double lon = y instanceof Double ? (Double) y : Double.valueOf((Long) y); + Double lat = x instanceof Double ? (Double) x : Double.valueOf((Long) x); + + Coordinate areaCoord = new Coordinate(lon, lat); + + Point point = geometryFactory.createPoint(areaCoord); + points.add(point); + } + } + } + public Geometry coordinateToGeometry(List target) { + if (target == null || target.isEmpty()) return null; + log.info(">>> {}", target.get(0) != target.get(target.size() - 1)); + if (target.get(0) != target.get(target.size() - 1)) { + target.add(target.get(0)); + } + return geometryFactory.createPolygon(target.toArray(new Coordinate[]{})); + } + +} diff --git a/web/api-flight/src/main/java/kr/co/palnet/kac/api/util/model/FlightPlanAreaCoordModel.java b/web/api-flight/src/main/java/kr/co/palnet/kac/api/util/model/FlightPlanAreaCoordModel.java new file mode 100644 index 0000000..0fefb15 --- /dev/null +++ b/web/api-flight/src/main/java/kr/co/palnet/kac/api/util/model/FlightPlanAreaCoordModel.java @@ -0,0 +1,22 @@ +package kr.co.palnet.kac.api.util.model; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class FlightPlanAreaCoordModel { + private Integer planAreaCoordSno; + private Integer planAreaSno; + @Schema(description = "x 좌표", example = "37.5208864") + private double lat; + @Schema(description = "y 좌표", example = "126.6071164") + private double lon; + @Schema(hidden = true) + private String createUserId; + @Schema(hidden = true) + private LocalDateTime createDt; + @Schema(hidden = true) + private String docState = "R"; +} diff --git a/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/controller/FlightLaancController.java b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/controller/FlightLaancController.java index dce7641..d72e734 100644 --- a/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/controller/FlightLaancController.java +++ b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/controller/FlightLaancController.java @@ -7,6 +7,10 @@ import kr.co.palnet.kac.api.v1.flight.laanc.model.create.CreateLaancPlanRS; import kr.co.palnet.kac.api.v1.flight.laanc.model.pliotvalid.PilotValidRs; import kr.co.palnet.kac.api.v1.flight.laanc.model.tsqr.LaancTsQRcodeRQ; import kr.co.palnet.kac.api.v1.flight.laanc.model.tsqr.LaancTsQRcodeRS; +import kr.co.palnet.kac.api.v1.flight.laanc.model.valid.LaancAllowableElevationRS; +import kr.co.palnet.kac.api.v1.flight.laanc.model.valid.LaancAreaByAirspaceModel; +import kr.co.palnet.kac.api.v1.flight.laanc.model.valid.LaancAreaByElevModel; +import kr.co.palnet.kac.api.v1.flight.laanc.model.valid.LaancDuplicatedAirspaceRs; import kr.co.palnet.kac.api.v1.flight.laanc.service.FlightLaancService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -14,6 +18,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @Slf4j @RequestMapping("/v1/flight/laanc") @@ -50,6 +56,26 @@ public class FlightLaancController { return ResponseEntity.ok().body(result); } + @PostMapping("/valid/duplicated/airspace") + @Operation(summary = "공역(금지구역) 체크", description = "공역(금지구역)에 현좌표가 포함되어있는지 확인합니다.") + public ResponseEntity> getDuplicatedAirspace(@RequestBody List rq) { + + List result = flightLaancService.getDuplicatedAirspace(rq); + + return ResponseEntity.ok().body(result); + } + + @PostMapping("/valid/elev") + @Operation(summary = "비행구역 허용고도 조회", description = "좌표에 해당하는 허용고도 조회") + public ResponseEntity getAllowableElevation(@RequestBody List rq){ + + LaancAllowableElevationRS result = flightLaancService.getAllowableElevation(rq); + + return ResponseEntity.ok().body(result); + } + + + diff --git a/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAllowableElevationRS.java b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAllowableElevationRS.java new file mode 100644 index 0000000..0a3b36e --- /dev/null +++ b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAllowableElevationRS.java @@ -0,0 +1,11 @@ +package kr.co.palnet.kac.api.v1.flight.laanc.model.valid; + +import lombok.Data; + +import java.util.List; + +@Data +public class LaancAllowableElevationRS { + + private List allowableElevation; +} diff --git a/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaByAirspaceModel.java b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaByAirspaceModel.java new file mode 100644 index 0000000..9b9cc2a --- /dev/null +++ b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaByAirspaceModel.java @@ -0,0 +1,21 @@ +package kr.co.palnet.kac.api.v1.flight.laanc.model.valid; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class LaancAreaByAirspaceModel { + + @Schema(description = "구역종류", example = "CIRCLE") + private String areaType; + + @Schema(description = "버퍼존", example = "100") + private Integer bufferZone; + + @Schema(description = "고도", example = "110") + private String fltElev; + + private List coordList; +} diff --git a/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaByElevModel.java b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaByElevModel.java new file mode 100644 index 0000000..4b76eee --- /dev/null +++ b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaByElevModel.java @@ -0,0 +1,19 @@ +package kr.co.palnet.kac.api.v1.flight.laanc.model.valid; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +@Data +public class LaancAreaByElevModel { + + @Schema(description = "구역종류", example = "CIRCLE") + private String areaType; + + @Schema(description = "버퍼존", example = "100") + private Integer bufferZone; + + private List coordList; + +} diff --git a/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaCoordModel.java b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaCoordModel.java new file mode 100644 index 0000000..6e45bfb --- /dev/null +++ b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancAreaCoordModel.java @@ -0,0 +1,14 @@ +package kr.co.palnet.kac.api.v1.flight.laanc.model.valid; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +public class LaancAreaCoordModel { + + @Schema(description = "위도", example = "127.33") + private double lat; + + @Schema(description = "경도", example = "37.99") + private double lon; +} diff --git a/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancDuplicatedAirspaceRs.java b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancDuplicatedAirspaceRs.java new file mode 100644 index 0000000..2852a8f --- /dev/null +++ b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/model/valid/LaancDuplicatedAirspaceRs.java @@ -0,0 +1,15 @@ +package kr.co.palnet.kac.api.v1.flight.laanc.model.valid; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class LaancDuplicatedAirspaceRs { + private boolean isDuplicated; + private LaancAreaByAirspaceModel rq; +} diff --git a/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/service/FlightLaancService.java b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/service/FlightLaancService.java index 9b82da7..ffc652d 100644 --- a/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/service/FlightLaancService.java +++ b/web/api-flight/src/main/java/kr/co/palnet/kac/api/v1/flight/laanc/service/FlightLaancService.java @@ -1,15 +1,16 @@ package kr.co.palnet.kac.api.v1.flight.laanc.service; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonMappingException; import kr.co.palnet.kac.api.external.model.TsQRcodeRQ; import kr.co.palnet.kac.api.external.service.CtrTrnsLctnService; import kr.co.palnet.kac.api.external.service.TsService; +import kr.co.palnet.kac.api.util.*; import kr.co.palnet.kac.api.v1.flight.laanc.model.create.CstmrModel; import kr.co.palnet.kac.api.v1.flight.laanc.model.*; import kr.co.palnet.kac.api.v1.flight.laanc.model.create.*; import kr.co.palnet.kac.api.v1.flight.laanc.model.tsqr.LaancTsQRcodeRQ; import kr.co.palnet.kac.api.v1.flight.laanc.model.tsqr.LaancTsQRcodeRS; +import kr.co.palnet.kac.api.v1.flight.laanc.model.valid.*; import kr.co.palnet.kac.data.com.domain.ComConfirmBas; import kr.co.palnet.kac.data.com.service.ComConfirmDomainService; import kr.co.palnet.kac.data.other.service.OtherDomainService; @@ -30,9 +31,6 @@ import kr.co.palnet.kac.data.pty.service.PtyCstmrDomainService; import kr.co.palnet.kac.data.pty.service.PtyGroupDomainService; import kr.co.palnet.kac.data.pty.service.PtyTermsDomainService; import kr.co.palnet.kac.api.external.service.ComnSmsService; -import kr.co.palnet.kac.api.util.DateUtils; -import kr.co.palnet.kac.api.util.FileUtils; -import kr.co.palnet.kac.api.util.HttpUtils; import kr.co.palnet.kac.api.util.model.LaancPdfModel; import kr.co.palnet.kac.util.EncryptUtil; import kr.co.palnet.kac.util.ObjectMapperUtils; @@ -45,11 +43,11 @@ import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; import com.google.zxing.WriterException; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Geometry; import java.io.IOException; -import java.time.Instant; import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; import java.util.*; @Service @@ -79,6 +77,8 @@ public class FlightLaancService { // private final FileUtils fileUtils; + private final AreaUtils areaUtils; + @Value("${app.host}") private String APP_HOST; @@ -419,4 +419,83 @@ public class FlightLaancService { return rs; } + + public List getDuplicatedAirspace(List rq) { + // TODO :: 임시로 모듈안에 Util 만들어서 사용, 추후 만들어진 것으로 사용할 것 + + AirspaceUtils airspaceUtils = AirspaceUtils.getInstance(); + List rs = new ArrayList<>(); + for (LaancAreaByAirspaceModel area : rq) { + + //rq로 들어온 좌표로 버퍼좌표 생성 + List targetCoord = new ArrayList<>(); + List targetCoordBufferList = new ArrayList<>(); + + for (LaancAreaCoordModel basLaancAreaCoordModel : area.getCoordList()) { + Coordinate coords = new Coordinate(basLaancAreaCoordModel.getLon(), basLaancAreaCoordModel.getLat()); + targetCoord.add(coords); + } + + if ("LINE".equals(area.getAreaType())) { + List trans = areaUtils.transform(targetCoord, "EPSG:4326", "EPSG:5181"); + List bufferList = areaUtils.buffer(trans, area.getBufferZone()); + targetCoordBufferList = areaUtils.transform(bufferList, "EPSG:5181", "EPSG:4326"); + } else if ("POLYGON".equals(area.getAreaType())) { + targetCoordBufferList.addAll(targetCoord); + } else if ("CIRCLE".equals(area.getAreaType())) { + targetCoordBufferList = areaUtils.createCircle(targetCoord.get(0), area.getBufferZone()); + } + + Geometry targetGeometry = airspaceUtils.createGeometryByCoordinate(targetCoordBufferList); + Integer fltElev = area.getFltElev() != null ? Integer.parseInt(area.getFltElev()) : 0; + AirspaceUtils.FeatureInfo targetFeatureInfo = new AirspaceUtils.FeatureInfo(null, null, 0, fltElev, targetGeometry); + boolean duplicatedAirspace = airspaceUtils.isDuplicatedAirspace(targetFeatureInfo); + LaancDuplicatedAirspaceRs duplicatedAirspaceRs = LaancDuplicatedAirspaceRs.builder() + .isDuplicated(duplicatedAirspace) + .rq(area) + .build(); + rs.add(duplicatedAirspaceRs); + } + + return rs; + } + + public LaancAllowableElevationRS getAllowableElevation(List rq) { + + + AirspaceUtils airspaceUtils = AirspaceUtils.getInstance(); + List allowElevationList = new ArrayList<>(); + for (LaancAreaByElevModel area : rq) { + + //rq로 들어온 좌표로 버퍼좌표 생성 + List targetCoord = new ArrayList<>(); + List targetCoordBufferList = new ArrayList<>(); + + for (LaancAreaCoordModel coord : area.getCoordList()) { + Coordinate coords = new Coordinate(coord.getLon(), coord.getLat()); + targetCoord.add(coords); + } + + if ("LINE".equals(area.getAreaType())) { + List trans = areaUtils.transform(targetCoord, "EPSG:4326", "EPSG:5181"); + List bufferList = areaUtils.buffer(trans, area.getBufferZone()); + targetCoordBufferList = areaUtils.transform(bufferList, "EPSG:5181", "EPSG:4326"); + } else if ("POLYGON".equals(area.getAreaType())) { + targetCoordBufferList.addAll(targetCoord); + } else if ("CIRCLE".equals(area.getAreaType())) { + targetCoordBufferList = areaUtils.createCircle(targetCoord.get(0), area.getBufferZone()); + } + + Geometry targetGeometry = airspaceUtils.createGeometryByCoordinate(targetCoordBufferList); +// Integer fltElev = area.getFltElev() != null ? Integer.parseInt(area.getFltElev()) : 0; +// AirspaceUtils.FeatureInfo featureInfo = new AirspaceUtils.FeatureInfo(null, null, 0, fltElev, rqGeometry); + Integer allowElevation = airspaceUtils.getAllowElevation(targetGeometry); + allowElevationList.add(allowElevation); + } + + LaancAllowableElevationRS result = new LaancAllowableElevationRS(); + result.setAllowableElevation(allowElevationList); + + return result; + } }