1 /******************************************************************************
2  * $Id: osm2osm.cpp 355b41831cd2685c85d1aabe5b95665a2c6e99b7 2019-06-19 17:07:04 +0200 Even Rouault $
3  *
4  * Project:  OpenGIS Simple Features Reference Implementation
5  * Author:   Even Rouault, <even dot rouault at spatialys.com>
6  * Purpose:  osm2osm
7  *
8  ******************************************************************************
9  * Copyright (c) 2012, Even Rouault <even dot rouault at spatialys.com>
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included
19  * in all copies or substantial portions of the Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  ****************************************************************************/
29 
30 #include "cpl_vsi.h"
31 
32 #include "osm_parser.h"
33 
34 #include <cstdio>
35 #include <ctime>
36 
37 constexpr GIntBig SECSPERMIN = 60L;
38 constexpr GIntBig MINSPERHOUR = 60L;
39 constexpr GIntBig HOURSPERDAY = 24L;
40 constexpr GIntBig SECSPERHOUR = SECSPERMIN * MINSPERHOUR;
41 constexpr GIntBig SECSPERDAY = SECSPERHOUR * HOURSPERDAY;
42 constexpr GIntBig DAYSPERWEEK = 7;
43 constexpr GIntBig MONSPERYEAR = 12;
44 
45 constexpr GIntBig EPOCH_YEAR = 1970;
46 constexpr GIntBig EPOCH_WDAY = 4;
47 constexpr GIntBig TM_YEAR_BASE = 1900;
48 constexpr GIntBig DAYSPERNYEAR = 365;
49 constexpr GIntBig DAYSPERLYEAR = 366;
50 
isleap(GIntBig y)51 static bool isleap( GIntBig y ) {
52     return
53         ((y % 4) == 0 &&
54          (y % 100) != 0) ||
55         (y % 400) == 0;
56 }
57 
LEAPS_THROUGH_END_OF(GIntBig y)58 static GIntBig LEAPS_THROUGH_END_OF( GIntBig y )
59 {
60     return y / 4 - y / 100 + y / 400;
61 }
62 
63 static const int mon_lengths[2][MONSPERYEAR] = {
64   {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
65   {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
66 } ;
67 
68 static const int year_lengths[2] = { DAYSPERNYEAR, DAYSPERLYEAR };
69 
70 /************************************************************************/
71 /*                   CPLUnixTimeToYMDHMS()                              */
72 /************************************************************************/
73 
74 /** Converts a time value since the Epoch (aka "unix" time) to a broken-down
75  *  UTC time.
76  *
77  * This function is similar to gmtime_r().
78  * This function will always set tm_isdst to 0.
79  *
80  * @param unixTime number of seconds since the Epoch.
81  * @param pRet address of the return structure.
82  *
83  * @return the structure pointed by pRet filled with a broken-down UTC time.
84  */
85 
86 static
myCPLUnixTimeToYMDHMS(GIntBig unixTime,struct tm * pRet)87 struct tm * myCPLUnixTimeToYMDHMS(GIntBig unixTime, struct tm* pRet)
88 {
89     GIntBig days = unixTime / SECSPERDAY;
90     GIntBig rem = unixTime % SECSPERDAY;
91 
92     while (rem < 0) {
93         rem += SECSPERDAY;
94         --days;
95     }
96 
97     pRet->tm_hour = static_cast<int>(rem / SECSPERHOUR);
98     rem = rem % SECSPERHOUR;
99     pRet->tm_min = static_cast<int>(rem / SECSPERMIN);
100     /*
101     ** A positive leap second requires a special
102     ** representation.  This uses "... ??:59:60" et seq.
103     */
104     pRet->tm_sec = static_cast<int>(rem % SECSPERMIN);
105     pRet->tm_wday = static_cast<int>((EPOCH_WDAY + days) % DAYSPERWEEK);
106     if( pRet->tm_wday < 0 )
107         pRet->tm_wday += DAYSPERWEEK;
108     int yleap = 0;
109     GIntBig y = EPOCH_YEAR;
110     while( days < 0 ||
111            days >= static_cast<GIntBig>(year_lengths[yleap = isleap(y)]) )
112     {
113         GIntBig newy = y + days / DAYSPERNYEAR;
114         if( days < 0 )
115             --newy;
116         days -= (newy - y) * DAYSPERNYEAR +
117             LEAPS_THROUGH_END_OF(newy - 1) -
118             LEAPS_THROUGH_END_OF(y - 1);
119         y = newy;
120     }
121     pRet->tm_year = static_cast<int>(y - TM_YEAR_BASE);
122     pRet->tm_yday = static_cast<int>(days);
123     const int* ip = mon_lengths[yleap];
124     for( pRet->tm_mon = 0; days >= static_cast<GIntBig>(ip[pRet->tm_mon]);
125          ++(pRet->tm_mon) )
126         days = days - static_cast<GIntBig>(ip[pRet->tm_mon]);
127 
128     pRet->tm_mday = static_cast<int>(days + 1);
129     pRet->tm_isdst = 0;
130 
131     return pRet;
132 }
133 
WriteEscaped(const char * pszStr,VSILFILE * fp)134 static void WriteEscaped(const char* pszStr, VSILFILE* fp)
135 {
136     GByte ch;
137     while( (ch = *(pszStr ++)) != 0 )
138     {
139         if( ch == '<' )
140         {
141             /* printf("&lt;"); */
142             VSIFPrintfL(fp, "&#60;");
143         }
144         else if( ch == '>' )
145         {
146             /* printf("&gt;"); */
147             VSIFPrintfL(fp, "&#62;");
148         }
149         else if( ch == '&' )
150         {
151             /* printf("&amp;"); */
152             VSIFPrintfL(fp, "&#38;");
153         }
154         else if( ch == '"' )
155         {
156             /* printf("&quot;"); */
157             VSIFPrintfL(fp, "&#34;");
158         }
159         else if( ch == '\'' )
160         {
161             VSIFPrintfL(fp, "&#39;");
162         }
163         else if( ch < 0x20 && ch != 0x9 && ch != 0xA && ch != 0xD )
164         {
165             /* These control characters are unrepresentable in XML format, */
166             /* so we just drop them.  #4117 */
167         }
168         else
169         {
170             VSIFWriteL(&ch, 1, 1, fp);
171         }
172     }
173 }
174 
175 #define WRITE_STR(str) VSIFWriteL(str, 1, strlen(str), fp)
176 #define WRITE_ESCAPED(str) WriteEscaped(str, fp)
177 
178 static
myNotifyNodesFunc(unsigned int nNodes,OSMNode * pasNodes,OSMContext *,void * user_data)179 void myNotifyNodesFunc( unsigned int nNodes, OSMNode* pasNodes,
180                         OSMContext* /* psOSMContext */, void* user_data )
181 {
182     VSILFILE* fp = static_cast<VSILFILE *>(user_data);
183 
184     for( int k = 0; k < static_cast<int>(nNodes); k++ )
185     {
186         const OSMNode* psNode = &pasNodes[k];
187 
188         WRITE_STR(" <node id=\"");
189         VSIFPrintfL(fp, "%d", static_cast<int>(psNode->nID));
190         WRITE_STR("\" lat=\"");
191         VSIFPrintfL(fp, "%.7f", psNode->dfLat);
192         WRITE_STR("\" lon=\"");
193         VSIFPrintfL(fp, "%.7f", psNode->dfLon);
194         WRITE_STR("\" version=\"");
195         VSIFPrintfL(fp, "%d", psNode->sInfo.nVersion);
196         WRITE_STR("\" changeset=\"");
197         VSIFPrintfL(fp, "%d", static_cast<int>(psNode->sInfo.nChangeset));
198         if (psNode->sInfo.nUID >= 0)
199         {
200             WRITE_STR("\" user=\"");
201             WRITE_ESCAPED(psNode->sInfo.pszUserSID);
202             WRITE_STR("\" uid=\"");
203             VSIFPrintfL(fp, "%d", psNode->sInfo.nUID);
204         }
205 
206         if( !(psNode->sInfo.bTimeStampIsStr) )
207         {
208             WRITE_STR("\" timestamp=\"");
209             struct tm mytm;
210             myCPLUnixTimeToYMDHMS(psNode->sInfo.ts.nTimeStamp, &mytm);
211             VSIFPrintfL(fp, "%04d-%02d-%02dT%02d:%02d:%02dZ",
212                     1900 + mytm.tm_year, mytm.tm_mon + 1, mytm.tm_mday,
213                     mytm.tm_hour, mytm.tm_min, mytm.tm_sec);
214         }
215         else if( psNode->sInfo.ts.pszTimeStamp != NULL &&
216                  psNode->sInfo.ts.pszTimeStamp[0] != '\0' )
217         {
218             WRITE_STR("\" timestamp=\"");
219             WRITE_STR(psNode->sInfo.ts.pszTimeStamp);
220         }
221 
222         if( psNode->nTags )
223         {
224             WRITE_STR("\">\n");
225             OSMTag *pasTags = psNode->pasTags;
226             for( unsigned int l = 0; l < psNode->nTags; l++ )
227             {
228                 WRITE_STR("  <tag k=\"");
229                 WRITE_ESCAPED(pasTags[l].pszK);
230                 WRITE_STR("\" v=\"");
231                 WRITE_ESCAPED(pasTags[l].pszV);
232                 WRITE_STR("\" />\n");
233             }
234             WRITE_STR(" </node>\n");
235         }
236         else
237         {
238             WRITE_STR("\"/>\n");
239         }
240     }
241 }
242 
243 static
myNotifyWayFunc(OSMWay * psWay,OSMContext *,void * user_data)244 void myNotifyWayFunc( OSMWay* psWay, OSMContext* /* psOSMContext */,
245                       void* user_data )
246 {
247     VSILFILE* fp = static_cast<VSILFILE *>(user_data);
248 
249     WRITE_STR(" <way id=\"");
250     VSIFPrintfL(fp, "%d", static_cast<int>(psWay->nID));
251     WRITE_STR("\" version=\"");
252     VSIFPrintfL(fp, "%d", psWay->sInfo.nVersion);
253     WRITE_STR("\" changeset=\"");
254     VSIFPrintfL(fp, "%d", static_cast<int>(psWay->sInfo.nChangeset));
255     if (psWay->sInfo.nUID >= 0)
256     {
257         WRITE_STR("\" uid=\"");
258         VSIFPrintfL(fp, "%d", psWay->sInfo.nUID);
259         WRITE_STR("\" user=\"");
260         WRITE_ESCAPED(psWay->sInfo.pszUserSID);
261     }
262 
263     if( !(psWay->sInfo.bTimeStampIsStr) )
264     {
265         WRITE_STR("\" timestamp=\"");
266         struct tm mytm;
267         myCPLUnixTimeToYMDHMS(psWay->sInfo.ts.nTimeStamp, &mytm);
268         VSIFPrintfL(fp, "%04d-%02d-%02dT%02d:%02d:%02dZ",
269                 1900 + mytm.tm_year, mytm.tm_mon + 1, mytm.tm_mday,
270                 mytm.tm_hour, mytm.tm_min, mytm.tm_sec);
271     }
272     else if( psWay->sInfo.ts.pszTimeStamp != NULL &&
273              psWay->sInfo.ts.pszTimeStamp[0] != '\0' )
274     {
275         WRITE_STR("\" timestamp=\"");
276         WRITE_STR(psWay->sInfo.ts.pszTimeStamp);
277     }
278 
279     WRITE_STR("\">\n");
280 
281     for( unsigned int l = 0; l < psWay->nRefs; l++ )
282         VSIFPrintfL(fp, "  <nd ref=\"%d\"/>\n",
283                     static_cast<int>(psWay->panNodeRefs[l]));
284 
285     for( unsigned int l = 0; l < psWay->nTags; l++ )
286     {
287         WRITE_STR("  <tag k=\"");
288         WRITE_ESCAPED(psWay->pasTags[l].pszK);
289         WRITE_STR("\" v=\"");
290         WRITE_ESCAPED(psWay->pasTags[l].pszV);
291         WRITE_STR("\" />\n");
292     }
293     VSIFPrintfL(fp, " </way>\n");
294 }
295 
296 static
myNotifyRelationFunc(OSMRelation * psRelation,OSMContext *,void * user_data)297 void myNotifyRelationFunc( OSMRelation* psRelation,
298                            OSMContext* /* psOSMContext */, void* user_data )
299 {
300     VSILFILE* fp = static_cast<VSILFILE *>(user_data);
301 
302 
303     WRITE_STR(" <relation id=\"");
304     VSIFPrintfL(fp, "%d", static_cast<int>(psRelation->nID));
305     WRITE_STR("\" version=\"");
306     VSIFPrintfL(fp, "%d", psRelation->sInfo.nVersion);
307     WRITE_STR("\" changeset=\"");
308     VSIFPrintfL(fp, "%d", static_cast<int>(psRelation->sInfo.nChangeset));
309     if( psRelation->sInfo.nUID >= 0 )
310     {
311         WRITE_STR("\" uid=\"");
312         VSIFPrintfL(fp, "%d", psRelation->sInfo.nUID);
313         WRITE_STR("\" user=\"");
314         WRITE_ESCAPED(psRelation->sInfo.pszUserSID);
315     }
316 
317     if( !(psRelation->sInfo.bTimeStampIsStr) )
318     {
319         struct tm mytm;
320         myCPLUnixTimeToYMDHMS(psRelation->sInfo.ts.nTimeStamp, &mytm);
321         WRITE_STR("\" timestamp=\"");
322         VSIFPrintfL(fp, "%04d-%02d-%02dT%02d:%02d:%02dZ",
323                 1900 + mytm.tm_year, mytm.tm_mon + 1, mytm.tm_mday,
324                 mytm.tm_hour, mytm.tm_min, mytm.tm_sec);
325     }
326     else if( psRelation->sInfo.ts.pszTimeStamp != NULL &&
327              psRelation->sInfo.ts.pszTimeStamp[0] != '\0' )
328     {
329         WRITE_STR("\" timestamp=\"");
330         WRITE_STR(psRelation->sInfo.ts.pszTimeStamp);
331     }
332 
333     WRITE_STR("\">\n");
334 
335     const OSMTag* pasTags = psRelation->pasTags;
336     const OSMMember* pasMembers = psRelation->pasMembers;
337 
338     for( unsigned int l = 0; l < psRelation->nMembers; l++ )
339     {
340         WRITE_STR("  <member type=\"");
341         VSIFPrintfL(fp, "%s",
342                     pasMembers[l].eType == MEMBER_NODE
343                     ? "node"
344                     : pasMembers[l].eType == MEMBER_WAY ? "way": "relation");
345         WRITE_STR("\" ref=\"");
346         VSIFPrintfL(fp, "%d", static_cast<int>(pasMembers[l].nID));
347         WRITE_STR("\" role=\"");
348         WRITE_ESCAPED(pasMembers[l].pszRole);
349         WRITE_STR("\"/>\n");
350     }
351 
352     for( unsigned int l = 0; l < psRelation->nTags; l++ )
353     {
354         WRITE_STR("  <tag k=\"");
355         WRITE_ESCAPED(pasTags[l].pszK);
356         WRITE_STR("\" v=\"");
357         WRITE_ESCAPED(pasTags[l].pszV);
358         WRITE_STR("\" />\n");
359     }
360     VSIFPrintfL(fp, " </relation>\n");
361 }
362 
363 static
myNotifyBoundsFunc(double dfXMin,double dfYMin,double dfXMax,double dfYMax,OSMContext *,void * user_data)364 void myNotifyBoundsFunc( double dfXMin, double dfYMin,
365                          double dfXMax, double dfYMax,
366                          OSMContext* /* psOSMContext */, void* user_data )
367 {
368     VSILFILE* fp = static_cast<VSILFILE *>(user_data);
369     VSIFPrintfL(fp, " <bounds minlat=\"%.7f\" minlon=\"%.7f\""
370                 " maxlat=\"%.7f\" maxlon=\"%.7f\"/>\n",
371                 dfYMin, dfXMin, dfYMax, dfXMax);
372 }
373 
main(int argc,char * argv[])374 int main( int argc, char* argv[] )
375 {
376     if( argc != 3 )
377     {
378         fprintf(stderr, "Usage: osm2osm input.pbf output.osm\n");  /*ok*/
379         exit(1);
380     }
381 
382     const char *pszSrcFilename = argv[1];
383     const char *pszDstFilename = argv[2];
384 
385     VSILFILE* fp = VSIFOpenL(pszDstFilename, "wt");
386     if( fp == NULL )
387     {
388         fprintf(stderr, "Cannot create %s.\n", pszDstFilename);  /*ok*/
389         exit(1);
390     }
391 
392     OSMContext* psContext = OSM_Open(pszSrcFilename,
393                                      myNotifyNodesFunc,
394                                      myNotifyWayFunc,
395                                      myNotifyRelationFunc,
396                                      myNotifyBoundsFunc,
397                                      fp);
398     if( psContext == NULL )
399     {
400         fprintf(stderr, "Cannot process %s.\n", pszSrcFilename);  /*ok*/
401         exit(1);
402     }
403 
404     VSIFPrintfL(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
405     VSIFPrintfL(fp, "<osm version=\"0.6\" generator=\"pbttoosm\">\n");
406 
407     while( OSM_ProcessBlock(psContext) == OSM_OK );
408 
409     VSIFPrintfL(fp, "</osm>\n");
410 
411     OSM_Close(psContext);
412 
413     VSIFCloseL(fp);
414 
415     return 0;
416 }
417