Просмотр исходного кода

feat(water): complete T2 facility GIS contracts (#6)

Kazerin 4 дней назад
Родитель
Сommit
f211a6229e

+ 51 - 7
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/WaterSupplyGisMap/service/impl/WaterSupplyGisMapServiceImpl.java

@@ -16,7 +16,6 @@ import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -24,6 +23,7 @@ import java.util.Set;
 
 @Service
 public class WaterSupplyGisMapServiceImpl implements WaterSupplyGisMapService {
+    private static final List<String> TYPE_ORDER = List.of("source", "plant", "pumpStation", "pipe", "user");
     private static final Set<String> SUPPORTED_TYPES = Set.of("source", "plant", "pumpStation", "pipe", "user");
     private static final Map<String, String> TYPE_NAMES = Map.of("source", "水源地", "plant", "水厂", "pumpStation", "泵站", "pipe", "供水管网", "user", "用水户");
 
@@ -62,18 +62,44 @@ public class WaterSupplyGisMapServiceImpl implements WaterSupplyGisMapService {
     @Override
     public List<GisOverviewItemDTO> getOverview() {
         List<GisOverviewItemDTO> rows = mapper.selectOverview();
-        if (rows == null || rows.isEmpty()) return Collections.emptyList();
+        Map<String, GisOverviewItemDTO> rowsByType = rows == null
+                ? Collections.emptyMap()
+                : rows.stream().collect(java.util.stream.Collectors.toMap(
+                        GisOverviewItemDTO::getFacilityType,
+                        item -> item,
+                        (left, right) -> left));
+        List<GisOverviewItemDTO> result = new ArrayList<>();
+        for (String type : TYPE_ORDER) {
+            GisOverviewItemDTO item = rowsByType.getOrDefault(type, emptyOverviewItem(type));
+            item.setTotalCount(zeroIfNull(item.getTotalCount()));
+            item.setGeoCompleteCount(zeroIfNull(item.getGeoCompleteCount()));
+            item.setGeoMissingCount(zeroIfNull(item.getGeoMissingCount()));
+            result.add(item);
+        }
         GisOverviewItemDTO total = new GisOverviewItemDTO();
         total.setFacilityType("total");
         total.setFacilityTypeName("合计");
-        total.setTotalCount(rows.stream().mapToLong(item -> item.getTotalCount() == null ? 0 : item.getTotalCount()).sum());
-        total.setGeoCompleteCount(rows.stream().mapToLong(item -> item.getGeoCompleteCount() == null ? 0 : item.getGeoCompleteCount()).sum());
-        total.setGeoMissingCount(rows.stream().mapToLong(item -> item.getGeoMissingCount() == null ? 0 : item.getGeoMissingCount()).sum());
-        List<GisOverviewItemDTO> result = new ArrayList<>(rows);
+        total.setTotalCount(result.stream().mapToLong(GisOverviewItemDTO::getTotalCount).sum());
+        total.setGeoCompleteCount(result.stream().mapToLong(GisOverviewItemDTO::getGeoCompleteCount).sum());
+        total.setGeoMissingCount(result.stream().mapToLong(GisOverviewItemDTO::getGeoMissingCount).sum());
         result.add(total);
         return result;
     }
 
+    private GisOverviewItemDTO emptyOverviewItem(String type) {
+        GisOverviewItemDTO item = new GisOverviewItemDTO();
+        item.setFacilityType(type);
+        item.setFacilityTypeName(TYPE_NAMES.get(type));
+        item.setTotalCount(0L);
+        item.setGeoCompleteCount(0L);
+        item.setGeoMissingCount(0L);
+        return item;
+    }
+
+    private long zeroIfNull(Long value) {
+        return value == null ? 0L : value;
+    }
+
     @Override
     public GisFeatureDTO toPipeFeature(Map<String, Object> row) {
         GisFeatureDTO feature = baseFeature("pipe", row);
@@ -90,6 +116,7 @@ public class WaterSupplyGisMapServiceImpl implements WaterSupplyGisMapService {
     private void addPointFeatures(GisFeatureCollectionDTO collection, String type, List<Map<String, Object>> rows) {
         if (rows == null) return;
         for (Map<String, Object> row : rows) {
+            if (!hasValues(row, "longitude", "latitude")) continue;
             GisFeatureDTO feature = baseFeature(type, row);
             Map<String, Object> geometry = new LinkedHashMap<>();
             geometry.put("type", "Point");
@@ -100,7 +127,15 @@ public class WaterSupplyGisMapServiceImpl implements WaterSupplyGisMapService {
     }
 
     private void addPipeFeatures(GisFeatureCollectionDTO collection, List<Map<String, Object>> rows) {
-        if (rows != null) for (Map<String, Object> row : rows) collection.getFeatures().add(toPipeFeature(row));
+        if (rows != null) {
+            for (Map<String, Object> row : rows) {
+                if (!hasCoordinate(row, "startLongitude", "start_longitude")
+                        || !hasCoordinate(row, "startLatitude", "start_latitude")
+                        || !hasCoordinate(row, "endLongitude", "end_longitude")
+                        || !hasCoordinate(row, "endLatitude", "end_latitude")) continue;
+                collection.getFeatures().add(toPipeFeature(row));
+            }
+        }
     }
 
     private GisFeatureDTO baseFeature(String type, Map<String, Object> row) {
@@ -132,4 +167,13 @@ public class WaterSupplyGisMapServiceImpl implements WaterSupplyGisMapService {
         Object value = value(row, keys);
         return value instanceof BigDecimal ? (BigDecimal) value : new BigDecimal(value.toString());
     }
+
+    private boolean hasValues(Map<String, Object> row, String... keys) {
+        for (String key : keys) if (value(row, key) == null) return false;
+        return true;
+    }
+
+    private boolean hasCoordinate(Map<String, Object> row, String... aliases) {
+        return value(row, aliases) != null;
+    }
 }

+ 27 - 0
pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/FacilityCoordinateModelTest.java

@@ -7,6 +7,8 @@ import org.junit.jupiter.api.Test;
 import java.io.InputStream;
 import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -114,4 +116,29 @@ class FacilityCoordinateModelTest {
                     "GIS SQL must not resolve water supply tables through search_path");
         }
     }
+
+    @Test
+    void kingbaseMigrationIsRepeatableAndOnlyAddsTheApprovedWgs84Coordinates() throws Exception {
+        Path workingDirectory = Path.of(System.getProperty("user.dir"));
+        Path migration = workingDirectory.resolve(
+                "pipe-network-service/sql/water_supply_facility_gis.sql");
+        if (!Files.exists(migration)) {
+            migration = workingDirectory.resolve("../sql/water_supply_facility_gis.sql").normalize();
+        }
+        assertTrue(Files.exists(migration), migration.toString());
+        String sql = Files.readString(migration, StandardCharsets.UTF_8);
+
+        assertEquals(12, sql.split("ADD COLUMN IF NOT EXISTS", -1).length - 1);
+        assertEquals(5, sql.split("CREATE INDEX IF NOT EXISTS", -1).length - 1);
+        assertTrue(sql.contains("ALTER TABLE app_user.water_source_info"));
+        assertTrue(sql.contains("ALTER TABLE app_user.water_plant_info"));
+        assertTrue(sql.contains("ALTER TABLE app_user.water_pump_station"));
+        assertTrue(sql.contains("ALTER TABLE app_user.water_pipe_info"));
+        assertTrue(sql.contains("ALTER TABLE app_user.water_user_info"));
+        assertTrue(sql.contains("longitude numeric(11,8)"));
+        assertTrue(sql.contains("latitude numeric(10,8)"));
+        assertFalse(sql.toLowerCase().contains("hydrant"));
+        assertFalse(sql.contains("消火栓"));
+        assertFalse(sql.toUpperCase().contains("CREATE TABLE"));
+    }
 }

+ 133 - 0
pipe-network-service/zksy-system/src/test/java/com/zksy/WaterSupply/WaterSupplyGisMap/WaterSupplyGisMapServiceTest.java

@@ -0,0 +1,133 @@
+package com.zksy.WaterSupply.WaterSupplyGisMap;
+
+import com.zksy.WaterSupply.WaterPlantInfo.domain.WaterPlantInfo;
+import com.zksy.WaterSupply.WaterPlantInfo.service.IWaterPlantInfoService;
+import com.zksy.WaterSupply.WaterSupplyGisMap.dto.GisOverviewItemDTO;
+import com.zksy.WaterSupply.WaterSupplyGisMap.dto.GisFeatureCollectionDTO;
+import com.zksy.WaterSupply.WaterSupplyGisMap.mapper.WaterSupplyGisMapMapper;
+import com.zksy.WaterSupply.WaterSupplyGisMap.service.impl.WaterSupplyGisMapServiceImpl;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class WaterSupplyGisMapServiceTest {
+
+    @Mock
+    private WaterSupplyGisMapMapper mapper;
+
+    @Mock
+    private IWaterPlantInfoService plantService;
+
+    @InjectMocks
+    private WaterSupplyGisMapServiceImpl service;
+
+    @Test
+    void overviewStillDescribesAllFacilityTypesWhenNoCoordinatesHaveBeenLoaded() {
+        when(mapper.selectOverview()).thenReturn(Collections.emptyList());
+
+        List<GisOverviewItemDTO> overview = service.getOverview();
+
+        assertEquals(List.of("source", "plant", "pumpStation", "pipe", "user", "total"),
+                overview.stream().map(GisOverviewItemDTO::getFacilityType).collect(Collectors.toList()));
+        overview.forEach(item -> {
+            assertEquals(0L, item.getTotalCount());
+            assertEquals(0L, item.getGeoCompleteCount());
+            assertEquals(0L, item.getGeoMissingCount());
+        });
+    }
+
+    @Test
+    void pointLayerOmitsRowsWithoutBothCoordinatesAndKeepsLongitudeFirst() {
+        Map<String, Object> complete = pointRow(1L, "完整水厂");
+        Map<String, Object> missingLatitude = pointRow(2L, "缺纬度水厂");
+        missingLatitude.put("latitude", null);
+        when(mapper.selectPlantFeatures()).thenReturn(List.of(complete, missingLatitude));
+
+        GisFeatureCollectionDTO features = service.getFeatures(Set.of("plant"));
+
+        assertEquals(1, features.getFeatures().size());
+        assertEquals("Point", features.getFeatures().get(0).getGeometry().get("type"));
+        assertEquals(List.of(new BigDecimal("113.12345678"), new BigDecimal("23.12345678")),
+                features.getFeatures().get(0).getGeometry().get("coordinates"));
+    }
+
+    @Test
+    void pipeLayerOmitsRowsWithoutFourCoordinatesAndKeepsEndpointOrder() {
+        Map<String, Object> complete = pipeRow(1L, "完整管线");
+        Map<String, Object> missingEndpoint = pipeRow(2L, "缺终点纬度管线");
+        missingEndpoint.put("end_latitude", null);
+        when(mapper.selectPipeFeatures()).thenReturn(List.of(complete, missingEndpoint));
+
+        GisFeatureCollectionDTO features = service.getFeatures(Set.of("pipe"));
+
+        assertEquals(1, features.getFeatures().size());
+        assertEquals("LineString", features.getFeatures().get(0).getGeometry().get("type"));
+        assertEquals(List.of(
+                        List.of(new BigDecimal("113.10000000"), new BigDecimal("23.10000000")),
+                        List.of(new BigDecimal("113.20000000"), new BigDecimal("23.20000000"))),
+                features.getFeatures().get(0).getGeometry().get("coordinates"));
+    }
+
+    @Test
+    void requestedFacilityTypesDoNotLoadUnselectedLayers() {
+        when(mapper.selectPlantFeatures()).thenReturn(Collections.emptyList());
+
+        service.getFeatures(Set.of("plant"));
+
+        verify(mapper).selectPlantFeatures();
+        verify(mapper, never()).selectSourceFeatures();
+        verify(mapper, never()).selectPumpStationFeatures();
+        verify(mapper, never()).selectPipeFeatures();
+        verify(mapper, never()).selectUserFeatures();
+    }
+
+    @Test
+    void detailReturnsTheExistingFacilityEvenWhenItHasNoCoordinates() {
+        WaterPlantInfo plant = new WaterPlantInfo();
+        plant.setId(9L);
+        plant.setPlantName("待补坐标水厂");
+        when(plantService.getById(9L)).thenReturn(plant);
+
+        Object detail = service.getDetail("plant", 9L);
+
+        assertSame(plant, detail);
+    }
+
+    private Map<String, Object> pointRow(Long id, String name) {
+        Map<String, Object> row = new HashMap<>();
+        row.put("id", id);
+        row.put("code", "WP" + id);
+        row.put("name", name);
+        row.put("status", "0");
+        row.put("location", "测试地址");
+        row.put("longitude", new BigDecimal("113.12345678"));
+        row.put("latitude", new BigDecimal("23.12345678"));
+        return row;
+    }
+
+    private Map<String, Object> pipeRow(Long id, String name) {
+        Map<String, Object> row = pointRow(id, name);
+        row.put("start_longitude", new BigDecimal("113.10000000"));
+        row.put("start_latitude", new BigDecimal("23.10000000"));
+        row.put("end_longitude", new BigDecimal("113.20000000"));
+        row.put("end_latitude", new BigDecimal("23.20000000"));
+        return row;
+    }
+}