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