diff --git a/org-ccalm-main.yml b/org-ccalm-main.yml index 74cf477..f49dccd 100644 --- a/org-ccalm-main.yml +++ b/org-ccalm-main.yml @@ -10,8 +10,8 @@ spring: application: name: org-ccalm-main datasource: - #url: jdbc:postgresql://10.0.0.1:5432/CCALM?ApplicationName=org_ccalm_main&connectTimeout=10000&socketTimeout=30000 - url: jdbc:postgresql://ccalm.org:5432/CCALM?ApplicationName=org_ccalm_main&ssl=true&sslmode=require&connectTimeout=10000&socketTimeout=10000 + url: jdbc:postgresql://10.0.0.1:5432/CCALM?ApplicationName=org_ccalm_main&connectTimeout=10000&socketTimeout=30000 + #url: jdbc:postgresql://ccalm.org:5432/CCALM?ApplicationName=org_ccalm_main&ssl=true&sslmode=require&connectTimeout=10000&socketTimeout=10000 #url: jdbc:postgresql://127.0.0.1:5432/CCALM?ApplicationName=org_ccalm_main&ssl=true&sslmode=require&connectTimeout=10000&socketTimeout=10000 username: postgres password: 309A86FF65A78FB428F4E38DFE35F730 diff --git a/src/main/java/org/ccalm/main/GeoGSON.java b/src/main/java/org/ccalm/main/GeoGSON.java index 624f4dc..b2f2bc7 100644 --- a/src/main/java/org/ccalm/main/GeoGSON.java +++ b/src/main/java/org/ccalm/main/GeoGSON.java @@ -1,6 +1,7 @@ package org.ccalm.main; import java.io.*; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; @@ -8,6 +9,7 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; @@ -16,10 +18,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.zaxxer.hikari.HikariDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.jdbc.core.JdbcTemplate; -//import javax.servlet.ServletContext; -//import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpServletRequest; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -253,31 +256,30 @@ public class GeoGSON implements ServletContextAware { // JSON to file frmlocust_pods_density.qgs @RequestMapping( - value = {"/gtest3","/geojson/frmlocust_pods_density", "/api/locust/v01/geojson/frmlocust_pods_density"}, + value = { "/geojson/frmlocust_pods_density", "/api/locust/v01/geojson/frmlocust_pods_density"}, method = RequestMethod.GET, - produces = "application/geo+json;charset=UTF-8" + produces = "application/geo+json;charset=UTF-8" ) @ResponseBody - public Object podsDensity( + public ResponseEntity podsDensity( @RequestParam(required = false, name = "country_id", defaultValue = "5") Integer countryId, @RequestParam(required = false, name = "date_from", defaultValue = "1750227418") Long dateFromUnix, - @RequestParam(required = false, name = "date_to", defaultValue = "1758010618") Long dateToUnix, - @CookieValue(value = "lng", defaultValue = "1") String language_id + @RequestParam(required = false, name = "date_to", defaultValue = "1758010618") Long dateToUnix, + @CookieValue(value = "lng", defaultValue = "1") String language_id, + HttpServletRequest request // Добавляем для получения заголовка Range ) { try { String sql = """ - SELECT id, - --country_name, - --region_name, - --date, - COALESCE(pods,0) as pods, - ST_AsGeoJSON(geom) AS geometry - FROM main.view_frmlocust_pods_density + SELECT + fl.id, + COALESCE(fl.eggs_capsules_density, fl.eggs_capsules_density_to)::double precision + COALESCE(fl.eggs_capsules_density_to, fl.eggs_capsules_density)::double precision / 2::double precision AS pods, + ST_AsGeoJSON(st_setsrid(st_makepoint(fl.lon_center, fl.lat_center), 4326)) AS geometry + FROM main.frmlocust fl WHERE 1=1 AND geom IS NOT NULL - AND (:countryId IS NULL OR country_id = :countryId) - AND (:dateFrom IS NULL OR date >= to_timestamp(:dateFrom)) - AND (:dateTo IS NULL OR date <= to_timestamp(:dateTo)) + AND (:countryId IS NULL OR fl.country_id = :countryId) + AND (:dateFrom IS NULL OR fl.date >= to_timestamp(:dateFrom)) + AND (:dateTo IS NULL OR fl.date <= to_timestamp(:dateTo)) """; MapSqlParameterSource params = new MapSqlParameterSource(); @@ -292,19 +294,20 @@ public class GeoGSON implements ServletContextAware { for (Map row : rows) { JSONObject feature = new JSONObject(); feature.put("type", "Feature"); - - // Геометрия feature.put("geometry", new JSONObject((String) row.get("geometry"))); - // Атрибуты JSONObject props = new JSONObject(); for (Map.Entry e : row.entrySet()) { if (!"geometry".equals(e.getKey())) { - props.put(e.getKey(), e.getValue()); + Object value = e.getValue(); + if (value == null) { + props.put(e.getKey(), JSONObject.NULL); + } else { + props.put(e.getKey(), value); + } } } feature.put("properties", props); - features.put(feature); } @@ -312,85 +315,168 @@ public class GeoGSON implements ServletContextAware { collection.put("type", "FeatureCollection"); collection.put("features", features); - return ResponseEntity.ok(collection.toString()); + // Преобразуем в байты + byte[] data = collection.toString().getBytes(StandardCharsets.UTF_8); + long contentLength = data.length; + + // Обрабатываем Range-запрос + String rangeHeader = request.getHeader("Range"); + HttpHeaders headers = new HttpHeaders(); + headers.add("Accept-Ranges", "bytes"); + + if (rangeHeader != null && rangeHeader.startsWith("bytes=")) { + String[] ranges = rangeHeader.replace("bytes=", "").split("-"); + long start = Long.parseLong(ranges[0]); + long end = ranges.length > 1 && !ranges[1].isEmpty() ? Long.parseLong(ranges[1]) : contentLength - 1; + + if (start >= contentLength || end >= contentLength || start > end) { + return ResponseEntity.status(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE) + .header("Content-Range", "bytes */" + contentLength) + .build(); + } + + long rangeLength = end - start + 1; + byte[] rangeData = new byte[(int) rangeLength]; + System.arraycopy(data, (int) start, rangeData, 0, (int) rangeLength); + + headers.add("Content-Range", "bytes " + start + "-" + end + "/" + contentLength); + headers.add("Content-Length", String.valueOf(rangeLength)); + + return new ResponseEntity<>(rangeData, headers, HttpStatus.PARTIAL_CONTENT); + } + + // Полный ответ, если Range не запрошен + headers.add("Content-Length", String.valueOf(contentLength)); + return new ResponseEntity<>(data, headers, HttpStatus.OK); } catch (Exception e) { e.printStackTrace(); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) - .body("{\"error\":\"" + e.getMessage() + "\"}"); + String error = "{\"error\":\"" + e.getMessage() + "\"}"; + return new ResponseEntity<>(error.getBytes(StandardCharsets.UTF_8), HttpStatus.INTERNAL_SERVER_ERROR); } } - @GetMapping(value = "/gtest", produces = "application/geo+json;charset=UTF-8") + @GetMapping( + value = { "/geojson/countriesdistricts", "/api/locust/v01/geojson/countriesdistricts"}, + produces = "application/geo+json;charset=UTF-8") @ResponseBody - public String getTestGeoJson() throws Exception { - ObjectMapper mapper = new ObjectMapper(); - // Формируем GeoJSON как объект - String geoJson = """ -{ - "type": "FeatureCollection", - "features": [ - { - "geometry": { - "coordinates": [70.15782, 42.2699], - "type": "Point" - }, - "type": "Feature", - "properties": { - "date": "2025-09-13 17:55:00.0", - "country_name": "Kazakhstan (Қазақстан)", - "region_name": "Түркістан облысы (Туркестанская область)", - "pods": 0, - "id": 455603 - } - }, - { - "geometry": { - "coordinates": [70.15782, 42.2699], - "type": "Point" - }, - "type": "Feature", - "properties": { - "date": "2025-09-13 17:55:00.0", - "country_name": "Kazakhstan (Қазақстан)", - "region_name": "Түркістан облысы (Туркестанская область)", - "pods": 0, - "id": 455602 - } - } - ] -} - """; - // Парсим и возвращаем для проверки корректности - return mapper.readTree(geoJson).toString(); + public ResponseEntity getCountriesDistricts( + @RequestParam(required = false, name = "country_id") Long countryId, + HttpServletRequest request + ) { + return getGeoJson("main.view_countriesdistricts", "id", countryId, request); } - @GetMapping(value = "/gtest2", produces = "application/geo+json;charset=UTF-8") + @GetMapping( + value = { "/geojson/countriesregions", "/api/locust/v01/geojson/countriesregions"}, + produces = "application/geo+json;charset=UTF-8" + ) @ResponseBody - public String getTestGeoJson2() throws Exception { - ObjectMapper mapper = new ObjectMapper(); - // Формируем GeoJSON как объект - String geoJson = """ - { - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "geometry": { - "type": "Point", - "coordinates": [70.15782, 42.2699] - }, - "properties": { - "id": 1, - "name": "Test point" - } - } - ] - } - """; - // Парсим и возвращаем для проверки корректности - return mapper.readTree(geoJson).toString(); + public ResponseEntity getCountriesRegions( + @RequestParam(required = false, name = "country_id") Long countryId, + HttpServletRequest request + ) { + return getGeoJson("main.view_countriesregions", "id", countryId, request); } + @GetMapping( + value = { "/geojson/countries", "/api/locust/v01/geojson/countries"}, + produces = "application/geo+json;charset=UTF-8") + @ResponseBody + public ResponseEntity getCountries( + @RequestParam(required = false, name = "country_id") Long countryId, + HttpServletRequest request + ) { + return getGeoJson("main.view_countries", "country_id", countryId, request); + } + + /** + * Общая функция для GeoJSON с поддержкой Range-запросов + */ + private ResponseEntity getGeoJson( + String table, + String idField, + Long countryId, + HttpServletRequest request + ) { + try { + String sql = """ + SELECT + %s AS id, + ST_AsGeoJSON(geom)::text AS geometry, + * + FROM %s + WHERE + geom IS NOT NULL + AND (country_id = :countryId) + """.formatted(idField, table); + + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("countryId", countryId); + + List> rows = jdbcTemplate.queryForList(sql, params); + + JSONArray features = new JSONArray(); + for (Map row : rows) { + JSONObject feature = new JSONObject(); + feature.put("type", "Feature"); + + // Геометрия + feature.put("geometry", new JSONObject((String) row.get("geometry"))); + + // Свойства + JSONObject props = new JSONObject(); + for (Map.Entry e : row.entrySet()) { + if (!"geometry".equals(e.getKey()) && !"geom".equals(e.getKey())) { + Object value = e.getValue(); + props.put(e.getKey(), value == null ? JSONObject.NULL : value); + } + } + feature.put("properties", props); + features.put(feature); + } + + JSONObject collection = new JSONObject(); + collection.put("type", "FeatureCollection"); + collection.put("features", features); + + byte[] data = collection.toString().getBytes(StandardCharsets.UTF_8); + long contentLength = data.length; + + HttpHeaders headers = new HttpHeaders(); + headers.add("Accept-Ranges", "bytes"); + + // Обработка Range + String rangeHeader = request.getHeader("Range"); + if (rangeHeader != null && rangeHeader.startsWith("bytes=")) { + String[] ranges = rangeHeader.replace("bytes=", "").split("-"); + long start = Long.parseLong(ranges[0]); + long end = ranges.length > 1 && !ranges[1].isEmpty() ? Long.parseLong(ranges[1]) : contentLength - 1; + + if (start >= contentLength || end >= contentLength || start > end) { + return ResponseEntity.status(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE) + .header("Content-Range", "bytes */" + contentLength) + .build(); + } + + long rangeLength = end - start + 1; + byte[] rangeData = new byte[(int) rangeLength]; + System.arraycopy(data, (int) start, rangeData, 0, (int) rangeLength); + + headers.add("Content-Range", "bytes " + start + "-" + end + "/" + contentLength); + headers.add("Content-Length", String.valueOf(rangeLength)); + + return new ResponseEntity<>(rangeData, headers, HttpStatus.PARTIAL_CONTENT); + } + + headers.add("Content-Length", String.valueOf(contentLength)); + return new ResponseEntity<>(data, headers, HttpStatus.OK); + + } catch (Exception e) { + e.printStackTrace(); + String error = "{\"error\":\"" + e.getMessage() + "\"}"; + return new ResponseEntity<>(error.getBytes(StandardCharsets.UTF_8), HttpStatus.INTERNAL_SERVER_ERROR); + } + } }