Skip to content

Commit

Permalink
feat: add epsg service
Browse files Browse the repository at this point in the history
  • Loading branch information
hwbllmnn committed Dec 20, 2024
1 parent 77e6199 commit 8ba1606
Show file tree
Hide file tree
Showing 9 changed files with 363 additions and 1 deletion.
16 changes: 16 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@

<!-- Jib -->
<image>docker-public.terrestris.de/shogun/${project.artifactId}:latest</image>
<geotools.version>33-SNAPSHOT</geotools.version>
</properties>

<build>
Expand Down Expand Up @@ -619,6 +620,21 @@
<version>${archunit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-referencing</artifactId>
<version>${geotools.version}</version>
</dependency>
<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-main</artifactId>
<version>${geotools.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
15 changes: 15 additions & 0 deletions shogun-boot/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,21 @@
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
</dependency>

<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-epsg-hsql</artifactId>
</dependency>

<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-referencing</artifactId>
</dependency>

<dependency>
<groupId>org.geotools</groupId>
<artifactId>gt-main</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* SHOGun, https://terrestris.github.io/shogun/
*
* Copyright © 2024-present terrestris GmbH & Co. KG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.terrestris.shogun.boot.controller;

import de.terrestris.shogun.boot.dto.ProjectionInfo;
import de.terrestris.shogun.boot.dto.TransformResult;
import de.terrestris.shogun.boot.service.ProjectionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.OK;

@Controller
@Log4j2
@Tag(
name = "Projections",
description = "Endpoints to work with projections"
)
public class ProjectionController {

@Autowired
private ProjectionService projectionService;

@GetMapping(path = "/epsg")
@Operation(
summary = "Return details for a projection. Follows the format of the https://epsg.io API"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "Ok: the details were found and returned"
),
@ApiResponse(
responseCode = "500",
description = "Internal Server Error: Something went wrong trying to access the CRS definition"
)
})
public ResponseEntity<ProjectionInfo> getProjectionDetails(@RequestParam(name = "q") String query) {
try {
return new ResponseEntity<>(projectionService.getProjectionDetails(query), OK);
} catch (Exception e) {
log.warn("Unable to find projection: {}", e.getMessage());
log.trace("Stack trace:", e);
return new ResponseEntity<>(INTERNAL_SERVER_ERROR);
}
}

@GetMapping(path = "/epsg/transform")
@Operation(
summary = "Transform a geometry from one projection to another"
)
@ApiResponses(value = {
@ApiResponse(
responseCode = "200",
description = "Ok: the geometry was successfully transformed"
),
@ApiResponse(
responseCode = "500",
description = "Internal Server Error: Something went wrong trying transform the geometry"
)
})
public ResponseEntity<TransformResult> transform(
@RequestParam(name = "source") String source,
@RequestParam(name = "target") String target,
@RequestParam(name = "wkt") String wkt
) {
try {
return new ResponseEntity<>(projectionService.transform(source, target, wkt), OK);
} catch (Exception e) {
log.warn("Unable to transform: {}", e.getMessage());
log.trace("Stack trace:", e);
return new ResponseEntity<>(INTERNAL_SERVER_ERROR);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* SHOGun, https://terrestris.github.io/shogun/
*
* Copyright © 2024-present terrestris GmbH & Co. KG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.terrestris.shogun.boot.dto;

import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
public class ProjectionDetails {

private String authority;

private String code;

private String name;

private String proj4;

private List<Double> bbox = new ArrayList<>();

private String unit;

private String area;

private String wkt;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* SHOGun, https://terrestris.github.io/shogun/
*
* Copyright © 2024-present terrestris GmbH & Co. KG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.terrestris.shogun.boot.dto;

import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public class ProjectionInfo {

private String status = "ok";

private int numberResult = 1;

private List<ProjectionDetails> results = new ArrayList<>();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* SHOGun, https://terrestris.github.io/shogun/
*
* Copyright © 2024-present terrestris GmbH & Co. KG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.terrestris.shogun.boot.dto;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class TransformResult {

private String wkt;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* SHOGun, https://terrestris.github.io/shogun/
*
* Copyright © 2024-present terrestris GmbH & Co. KG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.terrestris.shogun.boot.service;

import de.terrestris.shogun.boot.dto.ProjectionDetails;
import de.terrestris.shogun.boot.dto.ProjectionInfo;
import de.terrestris.shogun.boot.dto.TransformResult;
import lombok.extern.log4j.Log4j2;
import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.operation.TransformException;
import org.geotools.geometry.jts.JTS;
import org.geotools.metadata.iso.extent.GeographicBoundingBoxImpl;
import org.geotools.referencing.CRS;
import org.geotools.referencing.proj.PROJFormattable;
import org.geotools.referencing.proj.PROJFormatter;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.ParseException;
import org.locationtech.jts.io.WKTReader;
import org.locationtech.jts.io.WKTWriter;
import org.springframework.stereotype.Component;

@Component
@Log4j2
public class ProjectionService {

public ProjectionInfo getProjectionDetails(String query) throws FactoryException {
var projectionInfo = new ProjectionInfo();
var projectionDetails = new ProjectionDetails();
projectionInfo.getResults().add(projectionDetails);
var system = CRS.decode(query);
var identifier = system.getIdentifiers().iterator().next();
projectionDetails.setAuthority(identifier.getAuthority().getTitle().toString());
projectionDetails.setCode(identifier.getCode());
projectionDetails.setName(system.getName().toString());
var fmt = new PROJFormatter();
projectionDetails.setProj4(fmt.toPROJ((PROJFormattable) system));
var bbox = system.getDomainOfValidity();
var bboxList = projectionDetails.getBbox();
for (var item : bbox.getGeographicElements()) {
var box = ((GeographicBoundingBoxImpl) item);
bboxList.add(box.getNorthBoundLatitude());
bboxList.add(box.getWestBoundLongitude());
bboxList.add(box.getSouthBoundLatitude());
bboxList.add(box.getEastBoundLongitude());
}
projectionDetails.setUnit(system.getCoordinateSystem().getAxis(0).getUnit().getName().toLowerCase());
projectionDetails.setArea(bbox.getDescription().toString());
projectionDetails.setWkt(system.toWKT());
return projectionInfo;
}

public TransformResult transform(String sourceCrs, String targetCrs, String wkt) throws FactoryException, ParseException, TransformException {
var source = CRS.decode(sourceCrs);
var target = CRS.decode(targetCrs);
var geometry = new WKTReader().read(wkt);
var transform = CRS.findMathTransform(source, target);
Geometry transformed = JTS.transform(geometry, transform);
var result = new WKTWriter().write(transformed);
return new TransformResult(result);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/* SHOGun, https://terrestris.github.io/shogun/
*
* Copyright © 2024-present terrestris GmbH & Co. KG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.terrestris.shogun.boot.service;

import org.geotools.api.referencing.FactoryException;
import org.geotools.api.referencing.operation.TransformException;
import org.junit.jupiter.api.Test;
import org.locationtech.jts.io.ParseException;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

public class ProjectionServiceTest {

@Test
public void getProjectionTest() throws FactoryException {
var service = new ProjectionService();
var result = service.getProjectionDetails("EPSG:25832");
assertEquals(1, result.getNumberResult());
assertEquals("25832", result.getResults().getFirst().getCode());
}

@Test
public void transformTest() throws FactoryException, TransformException, ParseException {
var service = new ProjectionService();
var result = service.transform("EPSG:4326", "EPSG:25832", "POINT(45 7)");
assertTrue(result.getWkt().contains("342369"));
assertTrue(result.getWkt().contains("4984896"));
assertTrue(result.getWkt().startsWith("POINT"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ default void customHttpConfiguration(HttpSecurity http) throws Exception {
"/v3/api-docs",
"/v3/api-docs/swagger-config",
// Enable anonymous access to GraphiQL
"/graphiql/**"
"/graphiql/**",
"/epsg/**"
)
.permitAll()
// Enable anonymous read access to entity endpoints (secured via permission evaluators)
Expand Down

0 comments on commit 8ba1606

Please sign in to comment.