1 /******************************************************************************
2 * Copyright (c) 2016, Howard Butler (howard@hobu.co)
3 *
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following
8 * conditions are met:
9 *
10 * * Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in
14 * the documentation and/or other materials provided
15 * with the distribution.
16 * * Neither the name of Hobu, Inc. or Flaxen Geo Consulting nor the
17 * names of its contributors may be used to endorse or promote
18 * products derived from this software without specific prior
19 * written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
28 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
32 * OF SUCH DAMAGE.
33 ****************************************************************************/
34
35 #pragma warning(push)
36 #pragma warning(disable: 4251)
37 #include <ogr_api.h>
38 #include <ogr_geometry.h>
39 #pragma warning(pop)
40
41 #include <pdal/Geometry.hpp>
42 #include <pdal/private/gdal/GDALUtils.hpp>
43
44 #include "private/SrsTransform.hpp"
45
46 namespace pdal
47 {
48
throwNoGeos()49 void Geometry::throwNoGeos()
50 {
51 if (!OGRGeometryFactory::haveGEOS())
52 throw pdal_error("PDAL must be using a version of GDAL built with "
53 "GEOS support to use this function.");
54 }
55
56
Geometry()57 Geometry::Geometry()
58 {}
59
60
Geometry(const std::string & wkt_or_json,SpatialReference ref)61 Geometry::Geometry(const std::string& wkt_or_json, SpatialReference ref)
62 {
63 update(wkt_or_json);
64 if (ref.valid())
65 setSpatialReference(ref);
66 }
67
68
Geometry(const Geometry & input)69 Geometry::Geometry(const Geometry& input) : m_geom(input.m_geom->clone())
70 {}
71
72
Geometry(Geometry && input)73 Geometry::Geometry(Geometry&& input) : m_geom(std::move(input.m_geom))
74 {}
75
76
Geometry(OGRGeometryH g)77 Geometry::Geometry(OGRGeometryH g) :
78 m_geom((reinterpret_cast<OGRGeometry *>(g))->clone())
79 {}
80
81
Geometry(OGRGeometryH g,const SpatialReference & srs)82 Geometry::Geometry(OGRGeometryH g, const SpatialReference& srs) :
83 m_geom((reinterpret_cast<OGRGeometry *>(g))->clone())
84 {
85 setSpatialReference(srs);
86 }
87
88
~Geometry()89 Geometry::~Geometry()
90 {}
91
92
modified()93 void Geometry::modified()
94 {}
95
96
update(const std::string & wkt_or_json)97 void Geometry::update(const std::string& wkt_or_json)
98 {
99 bool isJson = (wkt_or_json.find("{") != wkt_or_json.npos) ||
100 (wkt_or_json.find("}") != wkt_or_json.npos);
101
102 OGRGeometry *newGeom;
103 std::string srs;
104 if (isJson)
105 {
106 newGeom = gdal::createFromGeoJson(wkt_or_json, srs);
107 if (!newGeom)
108 throw pdal_error("Unable to create geometry from input GeoJSON");
109 }
110 else
111 {
112 newGeom = gdal::createFromWkt(wkt_or_json, srs);
113 if (!newGeom)
114 throw pdal_error("Unable to create geometry from input WKT");
115 }
116
117 // m_geom may be null if update() is called from a ctor.
118 if (newGeom->getSpatialReference() && srs.size())
119 throw pdal_error("Geometry contains spatial reference and one was "
120 "also provided following the geometry specification.");
121 if (!newGeom->getSpatialReference() && srs.size())
122 newGeom->assignSpatialReference(
123 new OGRSpatialReference(SpatialReference(srs).getWKT().data()));
124 // m_geom may be null if update() is called from a ctor.
125 else if (m_geom)
126 newGeom->assignSpatialReference(m_geom->getSpatialReference());
127 m_geom.reset(newGeom);
128 modified();
129 }
130
131
operator =(const Geometry & input)132 Geometry& Geometry::operator=(const Geometry& input)
133 {
134 if (m_geom != input.m_geom)
135 m_geom.reset(input.m_geom->clone());
136 modified();
137 return *this;
138 }
139
140
srsValid() const141 bool Geometry::srsValid() const
142 {
143 OGRSpatialReference *srs = m_geom->getSpatialReference();
144 return srs && srs->GetRoot();
145 }
146
147
transform(SpatialReference out)148 Utils::StatusWithReason Geometry::transform(SpatialReference out)
149 {
150 using namespace Utils;
151
152 if (!srsValid() && out.empty())
153 return StatusWithReason();
154
155 if (!srsValid())
156 return StatusWithReason(-2,
157 "Geometry::transform() failed. NULL source SRS.");
158 if (out.empty())
159 return StatusWithReason(-2,
160 "Geometry::transform() failed. NULL target SRS.");
161
162 OGRSpatialReference *inSrs = m_geom->getSpatialReference();
163 SrsTransform transform(*inSrs, OGRSpatialReference(out.getWKT().data()));
164 if (m_geom->transform(transform.get()) != OGRERR_NONE)
165 return StatusWithReason(-1, "Geometry::transform() failed.");
166 modified();
167 return StatusWithReason();
168 }
169
170
setSpatialReference(const SpatialReference & srs)171 void Geometry::setSpatialReference(const SpatialReference& srs)
172 {
173 OGRSpatialReference *oSrs;
174
175 if (!srs.valid())
176 oSrs = new OGRSpatialReference();
177 else
178 oSrs = new OGRSpatialReference(srs.getWKT().data());
179 m_geom->assignSpatialReference(oSrs);
180 oSrs->Release();
181 }
182
183
getSpatialReference() const184 SpatialReference Geometry::getSpatialReference() const
185 {
186 SpatialReference srs;
187
188 if (srsValid())
189 {
190 char *buf;
191 const char *options[] = { "FORMAT=WKT2", nullptr };
192 m_geom->getSpatialReference()->exportToWkt(&buf, options);
193 srs.set(buf);
194 CPLFree(buf);
195 }
196 return srs;
197 }
198
199
bounds() const200 BOX3D Geometry::bounds() const
201 {
202 OGREnvelope3D env;
203 m_geom->getEnvelope(&env);
204 return BOX3D(env.MinX, env.MinY, env.MinZ,
205 env.MaxX, env.MaxY, env.MaxZ);
206 }
207
208
valid() const209 bool Geometry::valid() const
210 {
211 throwNoGeos();
212
213 return (bool)m_geom->IsValid();
214 }
215
216
wkt(double precision,bool bOutputZ) const217 std::string Geometry::wkt(double precision, bool bOutputZ) const
218 {
219 // Important note: The precision is not always respected. Using GDAL
220 // it can only be set once. Because of this, there's no point in saving
221 // away the current OGR_WKT_PRECISION. Same for OGR_WKT_ROUND.
222 //
223 // Also note that when abs(value) < 1, f-type formatting is used.
224 // Otherwise g-type formatting is used. Precision means different things
225 // with the two format types. With f-formatting it specifies the
226 // number of places to the right of the decimal. In g-formatting, it's
227 // the minimum number of digits. Yuck.
228
229 std::string p(std::to_string((int)precision));
230 CPLSetConfigOption("OGR_WKT_PRECISION", p.data());
231 CPLSetConfigOption("OGR_WKT_ROUND", "FALSE");
232
233 char *buf;
234 OGRErr err = m_geom->exportToWkt(&buf);
235 if (err != OGRERR_NONE)
236 throw pdal_error("Geometry::wkt: unable to export geometry to WKT.");
237 std::string wkt(buf);
238 CPLFree(buf);
239 return wkt;
240 }
241
242
json(double precision) const243 std::string Geometry::json(double precision) const
244 {
245 CPLStringList aosOptions;
246 std::string p(std::to_string((int)precision));
247 aosOptions.SetNameValue("COORDINATE_PRECISION", p.data());
248
249 char* json = OGR_G_ExportToJsonEx(gdal::toHandle(m_geom.get()),
250 aosOptions.List());
251 std::string output(json);
252 OGRFree(json);
253 return output;
254 }
255
256
operator <<(std::ostream & ostr,const Geometry & p)257 std::ostream& operator<<(std::ostream& ostr, const Geometry& p)
258 {
259 ostr << p.wkt();
260 return ostr;
261 }
262
263
operator >>(std::istream & istr,Geometry & p)264 std::istream& operator>>(std::istream& istr, Geometry& p)
265 {
266 // Read stream into string.
267 std::string s(std::istreambuf_iterator<char>(istr), {});
268
269 try
270 {
271 p.update(s);
272 }
273 catch (pdal_error& )
274 {
275 istr.setstate(std::ios::failbit);
276 }
277 return istr;
278 }
279
280 } // namespace pdal
281