Skip to content

Commit 644c104

Browse files
committed
Add GeoAPI wrappers for GeographicCRS and its dependencies.
1 parent 9ebc772 commit 644c104

File tree

15 files changed

+1603
-0
lines changed

15 files changed

+1603
-0
lines changed

geoapi/pom.xml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
2+
<modelVersion>4.0.0</modelVersion>
3+
4+
<parent>
5+
<groupId>org.locationtech.proj4j</groupId>
6+
<artifactId>proj4j-modules</artifactId>
7+
<version>1.3.1-SNAPSHOT</version>
8+
</parent>
9+
10+
<groupId>org.locationtech.proj4j</groupId>
11+
<artifactId>proj4j-geoapi</artifactId>
12+
<packaging>jar</packaging>
13+
<name>GeoAPI wrappers</name>
14+
<description>GeoAPI wrappers for viewing PROJ4J as a GeoAPI implementation.</description>
15+
16+
<developers>
17+
<developer>
18+
<id>desruisseaux</id>
19+
<name>Martin Desruisseaux</name>
20+
<url>https://github.com/desruisseaux</url>
21+
</developer>
22+
</developers>
23+
24+
<dependencies>
25+
<dependency>
26+
<groupId>org.opengis</groupId>
27+
<artifactId>geoapi</artifactId>
28+
<version>3.0.2</version>
29+
</dependency>
30+
<dependency>
31+
<groupId>org.locationtech.proj4j</groupId>
32+
<artifactId>proj4j</artifactId>
33+
<version>${project.version}</version>
34+
</dependency>
35+
<dependency>
36+
<groupId>org.locationtech.proj4j</groupId>
37+
<artifactId>proj4j-epsg</artifactId>
38+
<version>${project.version}</version>
39+
<scope>test</scope>
40+
</dependency>
41+
<dependency>
42+
<groupId>tech.uom</groupId>
43+
<artifactId>seshat</artifactId>
44+
<version>1.3</version>
45+
<scope>test</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>junit</groupId>
49+
<artifactId>junit</artifactId>
50+
<scope>test</scope>
51+
</dependency>
52+
</dependencies>
53+
54+
<build>
55+
<plugins>
56+
<plugin>
57+
<groupId>org.apache.maven.plugins</groupId>
58+
<artifactId>maven-javadoc-plugin</artifactId>
59+
<configuration>
60+
<author>false</author>
61+
<noqualifier>all</noqualifier>
62+
<quiet>true</quiet>
63+
<breakiterator>true</breakiterator>
64+
<links>
65+
<link>https://www.geoapi.org/3.0/javadoc/</link>
66+
</links>
67+
</configuration>
68+
</plugin>
69+
</plugins>
70+
</build>
71+
72+
</project>
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2025, PROJ4J contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.locationtech.proj4j.geoapi;
17+
18+
import java.io.Serializable;
19+
import java.util.Arrays;
20+
import org.locationtech.proj4j.ProjCoordinate;
21+
import org.locationtech.proj4j.datum.AxisOrder;
22+
import org.locationtech.proj4j.proj.Projection;
23+
import org.opengis.referencing.crs.SingleCRS;
24+
import org.opengis.referencing.cs.CoordinateSystem;
25+
import org.opengis.referencing.cs.CoordinateSystemAxis;
26+
import org.opengis.referencing.datum.GeodeticDatum;
27+
28+
29+
/**
30+
* Wraps a PROJ4J implementation behind the equivalent GeoAPI interface.
31+
* The CRS is assumed two-dimensional.
32+
*
33+
* @author Martin Desruisseaux (Geomatys)
34+
*/
35+
@SuppressWarnings("serial")
36+
abstract class AbstractCRS extends Wrapper implements SingleCRS, CoordinateSystem, Serializable {
37+
/**
38+
* The number of dimensions of the CRS.
39+
*/
40+
private static final int BIDIMENSIONAL = 2;
41+
42+
/**
43+
* The wrapped PROJ4 implementation.
44+
*/
45+
private final org.locationtech.proj4j.CoordinateReferenceSystem impl;
46+
47+
/**
48+
* The coordinate system axes, computed and cached when first requested.
49+
* This is refreshed every time that {@link #getCoordinateSystem()} is invoked,
50+
* for compliance with the documentation saying that this object is a view.
51+
*/
52+
@SuppressWarnings("VolatileArrayField") // Because array elements will not change.
53+
private volatile transient Axis[] axes;
54+
55+
/**
56+
* Creates a new wrapper for the given PROJ4J implementation.
57+
*/
58+
AbstractCRS(final org.locationtech.proj4j.CoordinateReferenceSystem impl) {
59+
this.impl = impl;
60+
}
61+
62+
/**
63+
* Wraps the given implementation.
64+
*
65+
* @param impl the implementation to wrap, or {@code null}
66+
* @return the wrapper, or {@code null} if the given implementation was null
67+
*/
68+
static AbstractCRS wrap(final org.locationtech.proj4j.CoordinateReferenceSystem impl) {
69+
if (impl != null) {
70+
final Projection proj = impl.getProjection();
71+
if (proj == null || proj.isGeographic()) {
72+
return new GeographicCRSWrapper(impl);
73+
} else {
74+
// TODO
75+
}
76+
}
77+
return null;
78+
}
79+
80+
/**
81+
* {@return the PROJ4J backing implementation}.
82+
*/
83+
@Override
84+
final Object implementation() {
85+
return impl;
86+
}
87+
88+
/**
89+
* {@return the CRS name}.
90+
*/
91+
@Override
92+
public final String getCode() {
93+
return impl.getName();
94+
}
95+
96+
/**
97+
* {@return the PROJ4J datum wrapped behind the GeoAPI interface}.
98+
*/
99+
@Override
100+
public final GeodeticDatum getDatum() {
101+
return DatumWrapper.wrap(impl);
102+
}
103+
104+
/**
105+
* {@return the coordinate system, which is implemented by the same class for convenience}.
106+
*/
107+
@Override
108+
public CoordinateSystem getCoordinateSystem() {
109+
clearAxisCache();
110+
return this;
111+
}
112+
113+
/**
114+
* {@return the number of dimensions, which is fixed to 2}.
115+
*/
116+
@Override
117+
public final int getDimension() {
118+
return BIDIMENSIONAL;
119+
}
120+
121+
/**
122+
* Returns {@link Axis#GEOGRAPHIC} and {@link Axis#PROJECTED} arrays,
123+
* depending on whether this <abbr>CRS</abbr> is geographic or projected.
124+
* The returned array is not cloned, the caller shall not modify it.
125+
*/
126+
abstract Axis[] axesForAllDirections();
127+
128+
/**
129+
* Clears the cache of axes. This method should be invoked by {@link #getCoordinateSystem()}
130+
* for compliance with the documentation saying that change in the wrapped object are reflected
131+
* in the view.
132+
*/
133+
final void clearAxisCache() {
134+
axes = null;
135+
}
136+
137+
/**
138+
* Returns the axis in the given dimension.
139+
*
140+
* @param dimension the axis index, from 0 to 2 inclusive.
141+
* @return axis in the specified dimension.
142+
* @throws IndexOutOfBoundsException if the given axis index is out of bounds.
143+
*/
144+
@Override
145+
public final CoordinateSystemAxis getAxis(int dimension) {
146+
@SuppressWarnings("LocalVariableHidesMemberVariable")
147+
Axis[] axes = this.axes;
148+
if (axes == null) {
149+
final Axis[] axesForAllDirections = axesForAllDirections();
150+
axes = Arrays.copyOfRange(axesForAllDirections, Axis.INDEX_OF_EAST, axesForAllDirections.length);
151+
final Projection proj = impl.getProjection();
152+
if (proj != null) {
153+
final AxisOrder order = proj.getAxisOrder();
154+
if (order != null) {
155+
ProjCoordinate coord = new ProjCoordinate(1, 2, 3);
156+
order.fromENU(coord);
157+
for (int i=0; i<axes.length; i++) {
158+
final double c;
159+
switch (i) {
160+
case 0: c = coord.x; break;
161+
case 1: c = coord.y; break;
162+
case 2: c = coord.z; break;
163+
default: throw new AssertionError(i);
164+
}
165+
axes[i] = axesForAllDirections[((int) c) + (Axis.INDEX_OF_EAST - 1)];
166+
}
167+
}
168+
org.locationtech.proj4j.units.Unit unit = proj.getUnits();
169+
if (unit != null) {
170+
final double scale = unit.value;
171+
for (int i=0; i<axes.length; i++) {
172+
axes[i] = axes[i].withUnit(scale);
173+
}
174+
}
175+
}
176+
this.axes = axes;
177+
}
178+
return axes[dimension];
179+
}
180+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright 2025, PROJ4J contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.locationtech.proj4j.geoapi;
17+
18+
import java.io.Serializable;
19+
import java.util.Collection;
20+
import java.util.Collections;
21+
import java.util.List;
22+
import org.opengis.util.GenericName;
23+
import org.opengis.util.InternationalString;
24+
import org.opengis.util.LocalName;
25+
import org.opengis.util.NameSpace;
26+
import org.opengis.util.ScopedName;
27+
28+
29+
/**
30+
* An alternative name for an object.
31+
* Note that the EPSG database puts short names in aliases.
32+
* The long names are rather the primary object names.
33+
*
34+
* @author Martin Desruisseaux (Geomatys)
35+
*/
36+
@SuppressWarnings("serial")
37+
final class Alias implements LocalName, Serializable {
38+
/**
39+
* The name to provide as an alias.
40+
*/
41+
private final String name;
42+
43+
/**
44+
* Creates a new alias.
45+
*
46+
* @param name the name to provide as an alias.
47+
*/
48+
private Alias(final String name) {
49+
this.name = name;
50+
}
51+
52+
/**
53+
* Returns the given name as an alias.
54+
*
55+
* @param name the alias, or {@code null}
56+
* @return the alias, or an empty collection if the given name was null
57+
*/
58+
static Collection<GenericName> wrap(final String name) {
59+
return (name != null) ? Collections.singletonList(new Alias(name)) : Collections.emptyList();
60+
}
61+
62+
@Override
63+
public NameSpace scope() {
64+
return null;
65+
}
66+
67+
@Override
68+
public int depth() {
69+
return 1;
70+
}
71+
72+
@Override
73+
public List<LocalName> getParsedNames() {
74+
return Collections.singletonList(this);
75+
}
76+
77+
@Override
78+
public LocalName head() {
79+
return this;
80+
}
81+
82+
@Override
83+
public LocalName tip() {
84+
return this;
85+
}
86+
87+
@Override
88+
public GenericName toFullyQualifiedName() {
89+
return this;
90+
}
91+
92+
@Override
93+
public ScopedName push(GenericName scope) {
94+
throw new UnsupportedOperationException("Not supported.");
95+
}
96+
97+
@Override
98+
public InternationalString toInternationalString() {
99+
return LocalizedString.wrap(name);
100+
}
101+
102+
@Override
103+
public String toString() {
104+
return name;
105+
}
106+
107+
@Override
108+
public int compareTo(GenericName o) {
109+
int c = name.compareTo(o.head().toString());
110+
if (c == 0) {
111+
c = depth() - o.depth();
112+
}
113+
return c;
114+
}
115+
116+
@Override
117+
public boolean equals(Object o) {
118+
return (o instanceof Alias) && name.equals(((Alias) o).name);
119+
}
120+
121+
@Override
122+
public int hashCode() {
123+
return name.hashCode() ^ getClass().hashCode();
124+
}
125+
}

0 commit comments

Comments
 (0)