CockpitFacilityGeometryService.java 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. package com.zksy.cockpit.geometry;
  2. import com.zksy.base.domain.GasPipePoint;
  3. import com.zksy.base.domain.ManholeBase;
  4. import com.zksy.base.domain.PipeNetworkBase;
  5. import com.zksy.base.service.GasPipePointService;
  6. import com.zksy.base.service.ManholeBaseService;
  7. import com.zksy.base.service.PipeNetworkBaseService;
  8. import com.zksy.cockpit.foundation.CockpitCoordinateValidation;
  9. import com.zksy.cockpit.foundation.CockpitCoordinateValidator;
  10. import com.zksy.cockpit.foundation.CockpitPipeNetworkType;
  11. import com.zksy.cockpit.foundation.CockpitReadException;
  12. import com.zksy.cockpit.geometry.CockpitFacilityGeometryResponse.Coverage;
  13. import com.zksy.cockpit.geometry.CockpitFacilityGeometryResponse.Feature;
  14. import com.zksy.cockpit.geometry.CockpitFacilityGeometryResponse.Geometry;
  15. import com.zksy.cockpit.geometry.CockpitFacilityGeometryResponse.Properties;
  16. import org.springframework.stereotype.Service;
  17. import org.springframework.transaction.annotation.Transactional;
  18. import java.math.BigDecimal;
  19. import java.time.Clock;
  20. import java.time.ZoneId;
  21. import java.time.format.DateTimeFormatter;
  22. import java.util.ArrayList;
  23. import java.util.Arrays;
  24. import java.util.Collections;
  25. import java.util.List;
  26. import java.util.Objects;
  27. import java.util.stream.Collectors;
  28. @Service
  29. public class CockpitFacilityGeometryService {
  30. private final PipeNetworkBaseService networks;
  31. private final GasPipePointService gasPoints;
  32. private final ManholeBaseService manholes;
  33. private final Clock clock;
  34. public CockpitFacilityGeometryService(PipeNetworkBaseService networks, GasPipePointService gasPoints,
  35. ManholeBaseService manholes, Clock clock) {
  36. this.networks = networks;
  37. this.gasPoints = gasPoints;
  38. this.manholes = manholes;
  39. this.clock = clock;
  40. }
  41. @Transactional(readOnly = true)
  42. public CockpitFacilityGeometryResponse load() {
  43. try {
  44. List<Feature> features = new ArrayList<>();
  45. // IService.list() honours each entity's @TableLogic (del_flag=0).
  46. for (PipeNetworkBase network : Objects.requireNonNull(networks.list())) {
  47. String industry = CockpitPipeNetworkType.industryOf(network.getNetworkType());
  48. features.add(feature("pipe_network_base", network.getNetworkId(), network.getNetworkCode(),
  49. network.getNetworkName(), industry == null ? "UNKNOWN" : industry,
  50. CockpitPipeNetworkType.typeCodeOf(network.getNetworkType()), network.getNetworkType(),
  51. null, network.getNetworkId(), "LineString", Arrays.asList(
  52. pair(network.getStartLongitude(), network.getStartLatitude()),
  53. pair(network.getEndLongitude(), network.getEndLatitude()))));
  54. }
  55. for (GasPipePoint point : Objects.requireNonNull(gasPoints.list())) {
  56. features.add(feature("gas_pipe_point", String.valueOf(point.getPointId()), point.getPointCode(),
  57. point.getPointName(), "GAS", "GAS_PIPE_POINT", null, point.getPointType(),
  58. point.getNetworkId(), "Point",
  59. Collections.singletonList(pair(point.getLongitude(), point.getLatitude()))));
  60. }
  61. for (ManholeBase manhole : Objects.requireNonNull(manholes.list())) {
  62. features.add(feature("manhole_base", manhole.getManholeId(), manhole.getManholeCode(),
  63. manhole.getManholeName(), "MANHOLE", "MANHOLE", null, null,
  64. manhole.getNetworkId(), "Point",
  65. Collections.singletonList(pair(manhole.getLongitude(), manhole.getLatitude()))));
  66. }
  67. List<Coverage> coverage = new ArrayList<>();
  68. for (String industry : Arrays.asList("GAS", "WATER_SUPPLY", "DRAINAGE")) {
  69. coverage.add(coverage(features, industry, "pipe_network_base", "LineString", "TWO_ENDPOINTS_ONLY"));
  70. }
  71. coverage.add(coverage(features, "GAS", "gas_pipe_point", "Point", null));
  72. coverage.add(new Coverage("WATER_SUPPLY", null, "Point", "UNAVAILABLE",
  73. "NO_COMPONENT_SOURCE", 0, 0, 0));
  74. coverage.add(new Coverage("DRAINAGE", null, "Point", "UNAVAILABLE",
  75. "NO_TYPED_COMPONENT_SOURCE", 0, 0, 0));
  76. coverage.add(coverage(features, "MANHOLE", "manhole_base", "Point", "NO_COMPONENT_TYPE"));
  77. coverage.add(new Coverage("MANHOLE", null, "LineString", "UNAVAILABLE",
  78. "NO_PIPELINE_SOURCE", 0, 0, 0));
  79. coverage.add(coverage(features, "UNKNOWN", "pipe_network_base", "LineString", "UNKNOWN_NETWORK_TYPE"));
  80. String generatedAt = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(
  81. clock.instant().atZone(ZoneId.of("Asia/Shanghai")));
  82. return new CockpitFacilityGeometryResponse(generatedAt, features, coverage);
  83. } catch (RuntimeException exception) {
  84. // Never turn a failed source into an empty/partial successful collection.
  85. org.slf4j.LoggerFactory.getLogger(CockpitFacilityGeometryService.class)
  86. .error("Facility geometry source read failed", exception);
  87. throw new CockpitReadException("facility geometry source unavailable");
  88. }
  89. }
  90. private static Feature feature(String source, String sourceId, String code, String name, String industry,
  91. String typeCode, String networkType, String pointType, String networkId, String geometryType,
  92. List<List<BigDecimal>> coordinates) {
  93. List<CockpitCoordinateValidation> validations = coordinates.stream()
  94. .map(pair -> validate(pair.get(0), pair.get(1)))
  95. .collect(Collectors.toList());
  96. CockpitCoordinateValidation overall = validations.stream()
  97. .filter(validation -> "INVALID".equals(validation.getState()))
  98. .findFirst().orElse(validations.get(0));
  99. // Invalid GeoJSON coordinates cannot be represented as an empty or invented geometry.
  100. Geometry geometry = "INVALID".equals(overall.getState()) ? null : new Geometry(geometryType,
  101. "Point".equals(geometryType) ? coordinates.get(0) : coordinates);
  102. Properties properties = new Properties(source, sourceId, code, name, industry, typeCode, networkType,
  103. pointType, networkId, coordinates, overall, validations);
  104. return new Feature(source + ":" + sourceId, geometry, properties);
  105. }
  106. private static CockpitCoordinateValidation validate(BigDecimal longitude, BigDecimal latitude) {
  107. // Structural errors must remain distinguishable even without a verification flag.
  108. CockpitCoordinateValidation structural = CockpitCoordinateValidator.validate(longitude, latitude, true);
  109. return structural.isRenderable()
  110. ? CockpitCoordinateValidator.validate(longitude, latitude, false) : structural;
  111. }
  112. private static List<BigDecimal> pair(BigDecimal longitude, BigDecimal latitude) {
  113. return Arrays.asList(longitude, latitude);
  114. }
  115. private static Coverage coverage(List<Feature> features, String industry, String source,
  116. String geometryType, String reason) {
  117. List<Feature> records = features.stream().filter(feature ->
  118. industry.equals(feature.getProperties().getIndustry())
  119. && source.equals(feature.getProperties().getSource())).collect(Collectors.toList());
  120. int geometryCount = (int) records.stream().filter(feature -> feature.getGeometry() != null).count();
  121. int renderableCount = (int) records.stream()
  122. .filter(feature -> feature.getProperties().getCoordinateValidation().isRenderable()).count();
  123. return new Coverage(industry, source, geometryType, records.isEmpty() ? "EMPTY" : "AVAILABLE",
  124. reason, records.size(), geometryCount, renderableCount);
  125. }
  126. }