Patrick Brosi 4 gadi atpakaļ
vecāks
revīzija
b259c8859f

+ 6 - 3
src/osmfixer/FixerMain.cpp

@@ -19,6 +19,8 @@ int main(int argc, char** argv) {
19 19
   // initialize randomness
20 20
   srand(time(NULL) + rand());  // NOLINT
21 21
 
22
+  int port = 9090;
23
+
22 24
   if (argc < 2) {
23 25
     LOG(ERROR) << "Missing path to stations file.";
24 26
     exit(1);
@@ -27,8 +29,9 @@ int main(int argc, char** argv) {
27 29
   StatIdx idx;
28 30
   idx.readFromFile(argv[1]);
29 31
 
30
-  // start server
31
-
32
+  LOG(INFO) << "Starting server...";
32 33
   StatServer serv(&idx);
33
-  util::http::HttpServer(9090, &serv).run();
34
+
35
+  LOG(INFO) << "Listening on port " << port;
36
+  util::http::HttpServer(port, &serv).run();
34 37
 }

+ 196 - 14
src/osmfixer/index/StatIdx.cpp

@@ -9,11 +9,12 @@
9 9
 #include <string>
10 10
 #include "osmfixer/index/StatIdx.h"
11 11
 #include "util/geo/BezierCurve.h"
12
+#include "util/log/Log.h"
12 13
 
13
-using osmfixer::StatIdx;
14
+using osmfixer::Group;
14 15
 using osmfixer::OsmAttrs;
16
+using osmfixer::StatIdx;
15 17
 using osmfixer::Station;
16
-using osmfixer::Group;
17 18
 using osmfixer::Suggestion;
18 19
 
19 20
 // _____________________________________________________________________________
@@ -23,6 +24,7 @@ void StatIdx::readFromFile(const std::string& path) {
23 24
   std::string line;
24 25
 
25 26
   // first, parse nodes
27
+  LOG(INFO) << "Parsing nodes...";
26 28
   while (std::getline(f, line)) {
27 29
     // empty line is separator between blocks
28 30
     if (line.size() == 0) break;
@@ -50,8 +52,10 @@ void StatIdx::readFromFile(const std::string& path) {
50 52
 
51 53
     addStation(osmid, lat, lng, origGroup, group, attrs);
52 54
   }
55
+  LOG(INFO) << "Done.";
53 56
 
54 57
   // second, parse groups
58
+  LOG(INFO) << "Parsing groups... ";
55 59
   while (std::getline(f, line)) {
56 60
     // empty line is separator between blocks
57 61
     if (line.size() == 0) break;
@@ -65,7 +69,10 @@ void StatIdx::readFromFile(const std::string& path) {
65 69
     addGroup(osmid, attrs);
66 70
   }
67 71
 
72
+  LOG(INFO) << "Done.";
73
+
68 74
   // third, parse attr errs
75
+  LOG(INFO) << "Parsing errors... ";
69 76
   while (std::getline(f, line)) {
70 77
     // empty line is separator between blocks
71 78
     if (line.size() == 0) break;
@@ -87,10 +94,13 @@ void StatIdx::readFromFile(const std::string& path) {
87 94
 
88 95
     _stations[id].attrErrs.push_back({attr, otherAttr, conf, otherId});
89 96
   }
97
+  LOG(INFO) << "Done.";
90 98
 
99
+  LOG(INFO) << "Building indices...";
91 100
   initGroups();
92 101
   initSuggestions();
93 102
   initIndex();
103
+  LOG(INFO) << "Done.";
94 104
 }
95 105
 
96 106
 // _____________________________________________________________________________
@@ -99,7 +109,15 @@ void StatIdx::addStation(size_t osmid, double lat, double lng, size_t origGroup,
99 109
   auto point = util::geo::latLngToWebMerc<double>(lat, lng);
100 110
 
101 111
   _stations.emplace_back(
102
-      Station{_stations.size(), osmid, origGroup, group, point, attrs});
112
+      Station{_stations.size(),
113
+              osmid,
114
+              origGroup,
115
+              group,
116
+              point,
117
+              util::geo::webMercToLatLng<double>(point.getX(), point.getY()),
118
+              attrs,
119
+              {},
120
+              {}});
103 121
 
104 122
   // extend bounding box
105 123
   _bbox = util::geo::extendBox(point, _bbox);
@@ -119,19 +137,22 @@ void StatIdx::initGroups() {
119 137
   for (size_t i = 0; i < _stations.size(); i++) {
120 138
     // this should be ensured by the input file
121 139
     assert(_stations[i].origGroup < _groups.size());
122
-    _groups[_stations[i].origGroup].stations.push_back(i);
140
+    _groups[_stations[i].origGroup].polyStations.push_back(i);
141
+
142
+    _groups[_stations[i].group].stations.push_back(i);
123 143
 
124 144
     assert(_stations[i].group < _groups.size());
125 145
     if (_stations[i].group != _stations[i].origGroup &&
126 146
         _groups[_stations[i].group].osmid == 1) {
127
-      // only add non-orig groups to polygons of new (non-osm) groups
128
-      _groups[_stations[i].group].stations.push_back(i);
147
+      // only add non-orig stations to polygons of new (non-osm) groups
148
+      _groups[_stations[i].group].polyStations.push_back(i);
129 149
     }
130 150
   }
131 151
 
152
+  // build hull polygon
132 153
   for (size_t i = 0; i < _groups.size(); i++) {
133 154
     util::geo::MultiPoint<double> mp;
134
-    for (size_t stid : _groups[i].stations) {
155
+    for (size_t stid : _groups[i].polyStations) {
135 156
       double rad = 11.0;
136 157
       int n = 20;
137 158
       for (int i = 0; i < n; i++) {
@@ -150,16 +171,99 @@ void StatIdx::initGroups() {
150 171
 
151 172
 // _____________________________________________________________________________
152 173
 void StatIdx::initIndex() {
153
-  _sgrid = util::geo::Grid<size_t, util::geo::Point, double>(5000, 5000, _bbox,
154
-                                                             false);
155
-  _ggrid = util::geo::Grid<size_t, util::geo::Polygon, double>(5000, 5000,
174
+  double gSize = 10000;
175
+  _sgrid = util::geo::Grid<size_t, util::geo::Point, double>(gSize, gSize,
176
+                                                             _bbox, false);
177
+  _ggrid = util::geo::Grid<size_t, util::geo::Polygon, double>(gSize, gSize,
156 178
                                                                _bbox, false);
157
-  _suggrid = util::geo::Grid<size_t, util::geo::Line, double>(5000, 5000, _bbox,
158
-                                                              false);
179
+  _suggrid = util::geo::Grid<size_t, util::geo::Line, double>(gSize, gSize,
180
+                                                              _bbox, false);
181
+
159 182
   for (size_t i = 0; i < _stations.size(); i++) _sgrid.add(_stations[i].pos, i);
160 183
   for (size_t i = 0; i < _groups.size(); i++) _ggrid.add(_groups[i].poly, i);
161 184
   for (size_t i = 0; i < _suggestions.size(); i++)
162 185
     _suggrid.add(_suggestions[i].arrow, i);
186
+
187
+  _heatGridsOk.resize(10);
188
+  _heatGridsSugg.resize(10);
189
+  _heatGridsErr.resize(10);
190
+
191
+  for (size_t i = 0; i < 10; i++) {
192
+    _heatGridsOk[i] = (util::geo::Grid<osmfixer::Cluster, util::geo::Point, double>(
193
+        5000, 5000, _bbox, false));
194
+    _heatGridsSugg[i] = (util::geo::Grid<osmfixer::Cluster, util::geo::Point, double>(
195
+        5000, 5000, _bbox, false));
196
+    _heatGridsErr[i] = (util::geo::Grid<osmfixer::Cluster, util::geo::Point, double>(
197
+        5000, 5000, _bbox, false));
198
+    int step = 2000 * (i + 1);
199
+
200
+    for (size_t x = 1; x * step < (_sgrid.getXWidth() * gSize); x++) {
201
+      for (size_t y = 1; y * step < (_sgrid.getYHeight() * gSize); y++) {
202
+        std::set<size_t> tmp;
203
+        auto reqBox = util::geo::DBox({_bbox.getLowerLeft().getX() + (x - 1) * step, _bbox.getLowerLeft().getY() + (y - 1) * step}, {_bbox.getLowerLeft().getX() + x * step, _bbox.getLowerLeft().getY() + y * step});
204
+        _sgrid.get(reqBox, &tmp);
205
+
206
+        size_t countOk = 0;
207
+        double avgXOk = 0;
208
+        double avgYOk = 0;
209
+
210
+        size_t countSugg = 0;
211
+        double avgXSugg = 0;
212
+        double avgYSugg = 0;
213
+
214
+        size_t countErr = 0;
215
+        double avgXErr = 0;
216
+        double avgYErr = 0;
217
+
218
+        for (auto j : tmp) {
219
+          const auto& stat = _stations[j];
220
+          if (stat.suggestions.size() == 0 && stat.attrErrs.size() == 0) {
221
+            if (util::geo::contains(stat.pos, reqBox)) {
222
+              countOk++;
223
+              avgXOk += stat.pos.getX();
224
+              avgYOk += stat.pos.getY();
225
+            }
226
+          } else if (stat.suggestions.size() > 0 && stat.attrErrs.size() == 0) {
227
+            if (util::geo::contains(stat.pos, reqBox)) {
228
+              countSugg++;
229
+              avgXSugg += stat.pos.getX();
230
+              avgYSugg += stat.pos.getY();
231
+            }
232
+          } else if (stat.attrErrs.size() > 0) {
233
+            if (util::geo::contains(stat.pos, reqBox)) {
234
+              countErr++;
235
+              avgXErr += stat.pos.getX();
236
+              avgYErr += stat.pos.getY();
237
+            }
238
+          }
239
+        }
240
+
241
+        if (countOk != 0) {
242
+          avgXOk /= countOk;
243
+          avgYOk /= countOk;
244
+          auto ll = util::geo::webMercToLatLng<double>(avgXOk, avgYOk);
245
+
246
+          _heatGridsOk[i].add({avgXOk, avgYOk}, Cluster{{avgXOk, avgYOk}, ll, countOk});
247
+        }
248
+
249
+        if (countSugg != 0) {
250
+          avgXSugg /= countSugg;
251
+          avgYSugg /= countSugg;
252
+          auto ll = util::geo::webMercToLatLng<double>(avgXSugg, avgYSugg);
253
+
254
+          _heatGridsSugg[i].add({avgXSugg, avgYSugg}, Cluster{{avgXSugg, avgYSugg}, ll, countSugg});
255
+        }
256
+
257
+        if (countErr != 0) {
258
+          avgXErr /= countErr;
259
+          avgYErr /= countErr;
260
+          auto ll = util::geo::webMercToLatLng<double>(avgXErr, avgYErr);
261
+
262
+          _heatGridsErr[i].add({avgXErr, avgYErr}, Cluster{{avgXErr, avgYErr}, ll, countErr});
263
+        }
264
+      }
265
+    }
266
+  }
163 267
 }
164 268
 
165 269
 // _____________________________________________________________________________
@@ -196,7 +300,7 @@ std::vector<const Group*> StatIdx::getGroups(const util::geo::DBox bbox) const {
196 300
   _ggrid.get(reqBox, &tmp);
197 301
 
198 302
   for (auto i : tmp) {
199
-    if (util::geo::intersects(_groups[i].poly, reqBox))
303
+    // if (util::geo::intersects(_groups[i].poly, reqBox))
200 304
       ret.push_back(&_groups[i]);
201 305
   }
202 306
 
@@ -231,6 +335,83 @@ const Station* StatIdx::getStation(size_t id) const {
231 335
 }
232 336
 
233 337
 // _____________________________________________________________________________
338
+std::vector<osmfixer::Cluster> StatIdx::getHeatGridErr(const util::geo::DBox bbox,
339
+                                                    size_t z) const {
340
+  auto ll = util::geo::latLngToWebMerc<double>(bbox.getLowerLeft().getX(),
341
+                                               bbox.getLowerLeft().getY());
342
+  auto ur = util::geo::latLngToWebMerc<double>(bbox.getUpperRight().getX(),
343
+                                               bbox.getUpperRight().getY());
344
+
345
+  auto reqBox = util::geo::DBox(ll, ur);
346
+
347
+  z = fmin(9, z);
348
+  z = fmax(0, z);
349
+
350
+  const auto& grid = _heatGridsErr[9 - z];
351
+  std::vector<osmfixer::Cluster> ret;
352
+
353
+  std::set<osmfixer::Cluster> tmp;
354
+  grid.get(reqBox, &tmp);
355
+
356
+  for (const auto& i : tmp) {
357
+    if (util::geo::contains(i.pos, reqBox)) ret.push_back(i);
358
+  }
359
+
360
+  return ret;
361
+}
362
+
363
+// _____________________________________________________________________________
364
+std::vector<osmfixer::Cluster> StatIdx::getHeatGridOk(const util::geo::DBox bbox,
365
+                                                    size_t z) const {
366
+  auto ll = util::geo::latLngToWebMerc<double>(bbox.getLowerLeft().getX(),
367
+                                               bbox.getLowerLeft().getY());
368
+  auto ur = util::geo::latLngToWebMerc<double>(bbox.getUpperRight().getX(),
369
+                                               bbox.getUpperRight().getY());
370
+
371
+  auto reqBox = util::geo::DBox(ll, ur);
372
+
373
+  z = fmin(9, z);
374
+  z = fmax(0, z);
375
+
376
+  const auto& grid = _heatGridsOk[9 - z];
377
+  std::vector<osmfixer::Cluster> ret;
378
+
379
+  std::set<osmfixer::Cluster> tmp;
380
+  grid.get(reqBox, &tmp);
381
+
382
+  for (const auto& i : tmp) {
383
+    if (util::geo::contains(i.pos, reqBox)) ret.push_back(i);
384
+  }
385
+
386
+  return ret;
387
+}
388
+
389
+// _____________________________________________________________________________
390
+std::vector<osmfixer::Cluster> StatIdx::getHeatGridSugg(const util::geo::DBox bbox,
391
+                                                    size_t z) const {
392
+  auto ll = util::geo::latLngToWebMerc<double>(bbox.getLowerLeft().getX(),
393
+                                               bbox.getLowerLeft().getY());
394
+  auto ur = util::geo::latLngToWebMerc<double>(bbox.getUpperRight().getX(),
395
+                                               bbox.getUpperRight().getY());
396
+
397
+  auto reqBox = util::geo::DBox(ll, ur);
398
+
399
+  z = fmin(9, z);
400
+  z = fmax(0, z);
401
+
402
+  const auto& grid = _heatGridsSugg[9 - z];
403
+  std::vector<osmfixer::Cluster> ret;
404
+
405
+  std::set<osmfixer::Cluster> tmp;
406
+  grid.get(reqBox, &tmp);
407
+
408
+  for (const auto& i : tmp) {
409
+    if (util::geo::contains(i.pos, reqBox)) ret.push_back(i);
410
+  }
411
+
412
+  return ret;
413
+}
414
+// _____________________________________________________________________________
234 415
 void StatIdx::initSuggestions() {
235 416
   for (size_t i = 0; i < _stations.size(); i++) {
236 417
     auto& stat = _stations[i];
@@ -261,7 +442,8 @@ void StatIdx::initSuggestions() {
261 442
           stat.suggestions.push_back(_suggestions.size() - 1);
262 443
         }
263 444
       } else {
264
-        if (getGroup(stat.group)->osmid == 1) {
445
+        if (getGroup(stat.group)->osmid == 1 &&
446
+            getGroup(stat.group)->stations.size() > 1) {
265 447
           // move station from relation into new group
266 448
           sug.type = 3;
267 449
           sug.orig_gid = stat.origGroup;

+ 24 - 1
src/osmfixer/index/StatIdx.h

@@ -34,6 +34,7 @@ struct Station {
34 34
   size_t osmid;
35 35
   size_t origGroup, group;
36 36
   util::geo::DPoint pos;
37
+  util::geo::DPoint latLng;
37 38
   OsmAttrs attrs;
38 39
   std::vector<AttrErr> attrErrs;
39 40
   std::vector<size_t> suggestions;
@@ -45,6 +46,14 @@ struct Group {
45 46
   util::geo::DPolygon poly;
46 47
   OsmAttrs attrs;
47 48
   std::vector<size_t> stations;
49
+  std::vector<size_t> polyStations;
50
+};
51
+
52
+struct Cluster {
53
+  util::geo::DPoint pos;
54
+  util::geo::DPoint latLng;
55
+  size_t size;
56
+  bool operator<(const Cluster& other) const { return this < &other; }
48 57
 };
49 58
 
50 59
 class StatIdx {
@@ -55,12 +64,22 @@ class StatIdx {
55 64
 
56 65
   std::vector<const Station*> getStations(const util::geo::DBox bbox) const;
57 66
   std::vector<const Group*> getGroups(const util::geo::DBox bbox) const;
58
-  std::vector<const Suggestion*> getSuggestions(const util::geo::DBox bbox) const;
67
+  std::vector<const Suggestion*> getSuggestions(
68
+      const util::geo::DBox bbox) const;
59 69
 
60 70
   const Station* getStation(size_t id) const;
61 71
   const Group* getGroup(size_t id) const;
62 72
   const Suggestion* getSuggestion(size_t id) const;
63 73
 
74
+  std::vector<Cluster> getHeatGridOk(const util::geo::DBox bbox,
75
+      size_t z) const;
76
+
77
+  std::vector<Cluster> getHeatGridSugg(const util::geo::DBox bbox,
78
+      size_t z) const;
79
+
80
+  std::vector<Cluster> getHeatGridErr(const util::geo::DBox bbox,
81
+      size_t z) const;
82
+
64 83
  private:
65 84
   void addStation(size_t id, double lat, double lng, size_t origGroup,
66 85
                   size_t group, const OsmAttrs& attrs);
@@ -80,6 +99,10 @@ class StatIdx {
80 99
   util::geo::Grid<size_t, util::geo::Point, double> _sgrid;
81 100
   util::geo::Grid<size_t, util::geo::Polygon, double> _ggrid;
82 101
   util::geo::Grid<size_t, util::geo::Line, double> _suggrid;
102
+
103
+  std::vector<util::geo::Grid<Cluster, util::geo::Point, double>> _heatGridsOk;
104
+  std::vector<util::geo::Grid<Cluster, util::geo::Point, double>> _heatGridsSugg;
105
+  std::vector<util::geo::Grid<Cluster, util::geo::Point, double>> _heatGridsErr;
83 106
 };
84 107
 }  // namespace osmfixer
85 108
 

+ 37 - 33
src/osmfixer/server/StatServer.cpp

@@ -9,8 +9,8 @@
9 9
 #include "util/geo/Geo.h"
10 10
 #include "util/log/Log.h"
11 11
 
12
-using osmfixer::StatServer;
13 12
 using osmfixer::Params;
13
+using osmfixer::StatServer;
14 14
 
15 15
 // _____________________________________________________________________________
16 16
 util::http::Answer StatServer::handle(const util::http::Req& req,
@@ -57,6 +57,9 @@ util::http::Answer StatServer::handleHeatMapReq(const Params& pars) const {
57 57
   if (pars.count("cb")) cb = pars.find("cb")->second.c_str();
58 58
   auto box = util::split(pars.find("bbox")->second, ',');
59 59
 
60
+  double z = 0;
61
+  if (pars.count("z")) z = atoi(pars.find("z")->second.c_str());
62
+
60 63
   if (box.size() != 4) throw std::invalid_argument("Invalid request.");
61 64
 
62 65
   double lat1 = atof(box[0].c_str());
@@ -64,64 +67,66 @@ util::http::Answer StatServer::handleHeatMapReq(const Params& pars) const {
64 67
   double lat2 = atof(box[2].c_str());
65 68
   double lng2 = atof(box[3].c_str());
66 69
 
67
-  std::cout << pars.find("bbox")->second << std::endl;
68
-
69 70
   util::geo::DBox bbox(util::geo::DPoint(lat1, lng1),
70 71
                        util::geo::DPoint(lat2, lng2));
71 72
 
72 73
   std::stringstream json;
73 74
 
74
-  json << std::setprecision(10);
75
+  size_t p = 5;
76
+  if (z < 10) p = 4;
77
+  if (z < 8) p = 3;
78
+  if (z < 6) p = 2;
79
+
80
+  json << std::fixed << std::setprecision(p);
75 81
 
76 82
   if (cb.size()) json << cb << "(";
77 83
   json << "{\"ok\":[";
78 84
 
79
-  auto ret = _idx->getStations(bbox);
85
+  auto hgOk = _idx->getHeatGridOk(bbox, z);
80 86
   char sep = ' ';
81
-  for (auto stat : ret) {
82
-    if (stat->suggestions.size() != 0 || stat->attrErrs.size() != 0) continue;
87
+  for (auto cluster : hgOk) {
83 88
     json << sep;
84 89
     sep = ',';
85 90
 
86
-    auto latLng =
87
-        util::geo::webMercToLatLng<double>(stat->pos.getX(), stat->pos.getY());
88
-    json << "[" << latLng.getY() << "," << latLng.getX() << "," << "0.5" << "]";
91
+    json << "[" << cluster.latLng.getY() << "," << cluster.latLng.getX() << ","
92
+         << cluster.size << "]";
89 93
   }
90 94
 
91
-
92 95
   json << "],\"sugg\":[";
93 96
 
97
+  auto hgSugg = _idx->getHeatGridSugg(bbox, z);
98
+
94 99
   sep = ' ';
95
-  for (auto stat : ret) {
96
-    if (stat->suggestions.size() == 0) continue;
100
+  for (auto cluster : hgSugg) {
97 101
     json << sep;
98 102
     sep = ',';
99 103
 
100
-    auto latLng =
101
-        util::geo::webMercToLatLng<double>(stat->pos.getX(), stat->pos.getY());
102
-    json << "[" << latLng.getY() << "," << latLng.getX() << "," << "0.5" << "]";
104
+    json << "[" << cluster.latLng.getY() << "," << cluster.latLng.getX() << ","
105
+         << cluster.size << "]";
103 106
   }
104 107
 
105 108
   json << "],\"err\":[";
106 109
 
110
+  auto hgErr = _idx->getHeatGridErr(bbox, z);
111
+
107 112
   sep = ' ';
108
-  for (auto stat : ret) {
109
-    if (stat->attrErrs.size() == 0) continue;
113
+  for (auto cluster : hgErr) {
110 114
     json << sep;
111 115
     sep = ',';
112 116
 
113
-    auto latLng =
114
-        util::geo::webMercToLatLng<double>(stat->pos.getX(), stat->pos.getY());
115
-    json << "[" << latLng.getY() << "," << latLng.getX() << "," << "0.5" << "]";
117
+    json << "[" << cluster.latLng.getY() << "," << cluster.latLng.getX() << ","
118
+         << cluster.size << "]";
116 119
   }
117 120
 
118 121
   json << "]}";
119 122
 
120 123
   if (cb.size()) json << ")";
121 124
 
122
-  auto answ = util::http::Answer("200 OK", json.str(), true);
125
+  util::http::Answer answ = util::http::Answer("200 OK", json.str(), true);
123 126
   answ.params["Content-Type"] = "application/javascript; charset=utf-8";
124 127
 
128
+  LOG(INFO) << "Done.";
129
+
125 130
   return answ;
126 131
 }
127 132
 
@@ -140,16 +145,12 @@ util::http::Answer StatServer::handleMapReq(const Params& pars) const {
140 145
   double lat2 = atof(box[2].c_str());
141 146
   double lng2 = atof(box[3].c_str());
142 147
 
143
-  std::cout << pars.find("bbox")->second << std::endl;
144
-
145 148
   util::geo::DBox bbox(util::geo::DPoint(lat1, lng1),
146 149
                        util::geo::DPoint(lat2, lng2));
147 150
 
148
-  LOG(INFO) << "Request for bounding box " << util::geo::getWKT(bbox);
149
-
150 151
   std::stringstream json;
151 152
 
152
-  json << std::setprecision(10);
153
+  json << std::fixed << std::setprecision(6);
153 154
 
154 155
   if (cb.size()) json << cb << "(";
155 156
   json << "{\"stats\":[";
@@ -166,13 +167,13 @@ util::http::Answer StatServer::handleMapReq(const Params& pars) const {
166 167
   auto gret = _idx->getGroups(bbox);
167 168
   sep = ' ';
168 169
   for (auto group : gret) {
169
-    if (group->stations.size() < 2) continue;
170
+    if (group->polyStations.size() == 1) continue;
170 171
     json << sep;
171 172
     sep = ',';
172 173
     printGroup(group, &json);
173 174
   }
174 175
 
175
-  json << "], \"suggestions\":[";
176
+  json << "], \"su\":[";
176 177
   auto suggs = _idx->getSuggestions(bbox);
177 178
   sep = ' ';
178 179
   for (auto sugg : suggs) {
@@ -199,7 +200,7 @@ void StatServer::printStation(const Station* stat, std::ostream* out) const {
199 200
          << ",\"lon\":" << latLng.getX();
200 201
 
201 202
   if (stat->origGroup != stat->group || _idx->getGroup(stat->group)->osmid == 1)
202
-    (*out) << ",\"suggestion\":1";
203
+    (*out) << ",\"su\":1";
203 204
 
204 205
   if (stat->attrErrs.size())
205 206
     (*out) << ",\"attrerrs\":" << stat->attrErrs.size();
@@ -315,6 +316,8 @@ util::http::Answer StatServer::handleGroupReq(const Params& pars) const {
315 316
     const auto stat = _idx->getStation(sid);
316 317
     json << "{\"id\":" << sid << ","
317 318
          << "\"osmid\":" << stat->osmid << ","
319
+         << "\"group\":" << stat->group << ","
320
+         << "\"orig_group\":" << stat->origGroup << ","
318 321
          << "\"attrs\":{";
319 322
 
320 323
     char sep = ' ';
@@ -361,7 +364,7 @@ util::http::Answer StatServer::handleStatReq(const Params& pars) const {
361 364
 
362 365
   std::stringstream json;
363 366
 
364
-  json << std::setprecision(10);
367
+  json << std::fixed << std::setprecision(6);
365 368
 
366 369
   if (cb.size()) json << cb << "(";
367 370
   json << "{\"id\":" << sid << ","
@@ -404,7 +407,7 @@ util::http::Answer StatServer::handleStatReq(const Params& pars) const {
404 407
 
405 408
   json << "]";
406 409
 
407
-  json << ",\"suggestions\":[";
410
+  json << ",\"su\":[";
408 411
 
409 412
   // suggestions
410 413
   if (stat->group != stat->origGroup ||
@@ -418,7 +421,8 @@ util::http::Answer StatServer::handleStatReq(const Params& pars) const {
418 421
              << "}";
419 422
       }
420 423
     } else {
421
-      if (_idx->getGroup(stat->group)->osmid == 1) {
424
+      if (_idx->getGroup(stat->group)->osmid == 1 &&
425
+          _idx->getGroup(stat->group)->stations.size() > 1) {
422 426
         json << "{\"type\": 3,\"orig_gid\":" << stat->origGroup
423 427
              << ",\"orig_osm_rel_id\":"
424 428
              << _idx->getGroup(stat->origGroup)->osmid

+ 23 - 1
web/index.html

@@ -72,11 +72,33 @@
72 72
             margin-top: 10px;
73 73
         }
74 74
 
75
-        #sugg .sugtit {
75
+        #sugg .sugtit, .oldmemberstit, .newmemberstit {
76 76
             font-style: normal;
77 77
             font-weight: bold;
78 78
         }
79 79
 
80
+        #group-stations-new, #group-stations-old {
81
+                        margin-top: 8px;
82
+            padding-top: 4px;
83
+            border-top: solid 1px #AAA;
84
+        }
85
+
86
+        #group-stations-new div {
87
+            background-color: #0000ff36;
88
+            border-left: solid 4px #0000ff;
89
+            padding: 2px;
90
+            margin: 2px;
91
+            padding-left: 5px;
92
+        }
93
+
94
+        #group-stations-old div {
95
+            background-color: #00ff0036;
96
+            border-left: solid 4px #00ff00;
97
+            padding: 2px;
98
+            margin: 2px;
99
+            padding-left: 5px;
100
+        }
101
+
80 102
         #sugg {
81 103
             margin-top: 8px;
82 104
             padding-top: 4px;

+ 58 - 44
web/script.js

@@ -10,7 +10,7 @@ function marker(stat, z) {
10 10
             [stat.lat, stat.lon],
11 11
             {
12 12
                 color: 'black',
13
-                fillColor: stat.attrerrs ? 'red' : stat.suggestion ? 'blue' : '#78f378', 
13
+                fillColor: stat.attrerrs ? 'red' : stat.su ? 'blue' : '#78f378', 
14 14
                 radius: mwidths[23 - z],
15 15
                 fillOpacity : 1,
16 16
                 weight : 1,
@@ -21,7 +21,7 @@ function marker(stat, z) {
21 21
         return L.polyline(
22 22
             [[stat.lat, stat.lon], [stat.lat, stat.lon]],
23 23
             {
24
-                color: stat.attrerrs ? 'red' : stat.suggestion ? 'blue' : '#78f378',
24
+                color: stat.attrerrs ? 'red' : stat.su ? 'blue' : '#78f378',
25 25
                 weight: widths[15 - z],
26 26
                 opacity: opas[15 - z],
27 27
                 id : stat.id
@@ -31,8 +31,16 @@ function marker(stat, z) {
31 31
 }
32 32
 
33 33
 function poly(group, z) {
34
-    if (group.new) return L.polygon(group.poly, {color: "blue", smoothFactor : 0.4, fillOpacity:0.2, id : group.id})
35
-    return L.polygon(group.poly, {color: '#85f385', smoothFactor : 1, fillOpacity:0.2, id : group.id})
34
+    var style = {color: "#85f385", smoothFactor : 0.4, fillOpacity:0.2, id : group.id};
35
+    if (group.new) {
36
+        style.color = "blue";
37
+    }
38
+    if (z < 16) {
39
+        style.weight = 11;
40
+        style.opacity = 0.5;
41
+        style.fillOpacity = 0.5;
42
+    }
43
+    return L.polygon(group.poly, style)
36 44
 }
37 45
 
38 46
 function sugArr(sug, z) {
@@ -72,7 +80,7 @@ function renderStat(stat, ll) {
72 80
         tbody.appendChild(row);
73 81
         row.appendChild(col1);
74 82
         row.appendChild(col2);
75
-        col1.innerHTML = "<a href=\"https://wiki.openstreetmap.org/wiki/Key:" + key + "\" target=\"_blank\">" + key + "</a>";
83
+        col1.innerHTML = "<a href=\"https://wiki.openstreetmap.org/wiki/Key:" + key + "\" target=\"_blank\"><tt>" + key + "</tt></a>";
76 84
         for (var i = 0; i <  stat.attrs[key].length; i++) col2.innerHTML += "<span class='attrval'>" + stat.attrs[key][i] + "</span>"+ "<br>";
77 85
         attrrows[key] = row;
78 86
     }
@@ -93,25 +101,25 @@ function renderStat(stat, ll) {
93 101
         row.childNodes[1].appendChild(info);
94 102
     }
95 103
 
96
-    if (stat.suggestions.length) {        
104
+    if (stat.su.length) {        
97 105
         suggD.innerHTML = "<span class='sugtit'>Suggestions</span>";
98 106
     }
99 107
 
100
-    for (var i = 0; i < stat.suggestions.length; i++) {
101
-        var sugg = stat.suggestions[i];
108
+    for (var i = 0; i < stat.su.length; i++) {
109
+        var sugg = stat.su[i];
102 110
 
103 111
         var suggDiv = document.createElement('div');
104 112
         
105 113
         if (sugg.type == 1) {
106
-            suggDiv.innerHTML = "&bull; Move node into a <span class='grouplink' onmouseover='groupHl( " + sugg.target_gid + ")' onmouseout='groupUnHl( " + sugg.target_gid + ")'>new relation</span>";
114
+            suggDiv.innerHTML = "&bull; Move node into a <span class='grouplink' onmouseover='groupHl( " + sugg.target_gid + ")' onmouseout='groupUnHl( " + sugg.target_gid + ")'>new relation</span> <tt>public_transport=stop_area</tt>";
107 115
         } else if (sugg.type == 2) {
108 116
             suggDiv.innerHTML = "&bull; Move node into existing relation <a onmouseover='groupHl( " + sugg.target_gid + ")' onmouseout='groupUnHl( " + sugg.target_gid + ")' href=\"https://www.openstreetmap.org/relation/" + sugg.target_osm_rel_id + "\" target=\"_blank\">" + sugg.target_osm_rel_id + "</a>.";
109 117
         } else if (sugg.type == 3) {
110
-            suggDiv.innerHTML = "&bull; Move node from existing relation <a onmouseover='groupHl( " + sugg.target_gid + ")' onmouseout='groupUnHl( " + sugg.target_gid + ")' href=\"https://www.openstreetmap.org/relation/" + sugg.orig_osm_rel_id + "\" target=\"_blank\">" + sugg.orig_osm_rel_id + "</a> into new relation (internal id=" + sugg.target_gid + ")";
118
+            suggDiv.innerHTML = "&bull; Move node from existing relation <a onmouseover='groupHl( " + sugg.orig_gid + ")' onmouseout='groupUnHl( " + sugg.orig_gid + ")' href=\"https://www.openstreetmap.org/relation/" + sugg.orig_osm_rel_id + "\" target=\"_blank\">" + sugg.orig_osm_rel_id + "</a> into a <span class='grouplink' onmouseover='groupHl( " + sugg.target_gid + ")' onmouseout='groupUnHl( " + sugg.target_gid + ")'>new relation</span> <tt>public_transport=stop_area</tt>";
111 119
         } else if (sugg.type == 4) {
112
-            suggDiv.innerHTML = "&bull; Move node from existing relation <a onmouseover='groupHl( " + sugg.target_gid + ")' onmouseout='groupUnHl( " + sugg.target_gid + ")' href=\"https://www.openstreetmap.org/relation/" + sugg.orig_osm_rel_id + "\" target=\"_blank\">" + sugg.orig_osm_rel_id + "</a> into existing relation <a href=\"https://www.openstreetmap.org/relation/" + sugg.target_osm_rel_id + "\" target=\"_blank\">" + sugg.target_osm_rel_id + "</a>.";
120
+            suggDiv.innerHTML = "&bull; Move node from existing relation <a onmouseover='groupHl( " + sugg.orig_gid + ")' onmouseout='groupUnHl( " + sugg.orig_gid + ")' href=\"https://www.openstreetmap.org/relation/" + sugg.orig_osm_rel_id + "\" target=\"_blank\">" + sugg.orig_osm_rel_id + "</a> into existing relation <a href=\"https://www.openstreetmap.org/relation/" + sugg.target_osm_rel_id + "\" target=\"_blank\">" + sugg.target_osm_rel_id + "</a>.";
113 121
         } else if (sugg.type == 5) {
114
-            suggDiv.innerHTML = "&bull; Move node out of existing relation <a onmouseover='groupHl( " + sugg.target_gid + ")' onmouseout='groupUnHl( " + sugg.target_gid + ")' href=\"https://www.openstreetmap.org/relation/" + sugg.orig_osm_rel_id + "\" target=\"_blank\">" + sugg.orig_osm_rel_id + "</a>!";
122
+            suggDiv.innerHTML = "&bull; Move node out of existing relation <a onmouseover='groupHl( " + sugg.orig_gid + ")' onmouseout='groupUnHl( " + sugg.orig_gid + ")' href=\"https://www.openstreetmap.org/relation/" + sugg.orig_osm_rel_id + "\" target=\"_blank\">" + sugg.orig_osm_rel_id + "</a>!";
115 123
         }
116 124
 
117 125
         suggD.appendChild(suggDiv);
@@ -145,31 +153,37 @@ function renderGroup(grp, ll) {
145 153
     var attrrows = {};
146 154
 
147 155
     var content = document.createElement('div');
148
-    content.setAttribute("id", "nav")
149
-    var attrTbl = document.createElement('table');
150
-     attrTbl.setAttribute("id", "attr-tbl")
151
-    var suggD = document.createElement('table');
152
-    suggD.setAttribute("id", "sugg")
156
+    content.setAttribute("id", "nav");
153 157
 
158
+    var newMembers = document.createElement('div');
159
+    newMembers.setAttribute("id", "group-stations-new")
160
+    newMembers.innerHTML = "<span class='newmemberstit'>New Members</span>";
154 161
 
155
-    content.innerHTML = "Relation <a onmouseover='groupHl( " + grp.id + ")' onmouseout='groupUnHl( " + grp.id + ")' target='_blank' href='https://www.openstreetmap.org/relation/" + grp.osmid + "'>" + grp.osmid + "</a>";
162
+    var oldMembers = document.createElement('div');
163
+    oldMembers.setAttribute("id", "group-stations-old")
164
+    oldMembers.innerHTML = "<span class='oldmemberstit'>Existing Members</span>";
156 165
 
157
-    content.appendChild(attrTbl);
158
-    content.appendChild(suggD);
166
+    if (grp.osmid == 1) {
167
+        content.innerHTML = "<span class='grouplink' onmouseover='groupHl( " + grp.id + ")' onmouseout='groupUnHl( " + grp.id + ")'>New Relation <tt>public_transport=stop_area</tt></span>";
168
+    } else {
169
+        content.innerHTML = "OSM relation <a onmouseover='groupHl( " + grp.id + ")' onmouseout='groupUnHl( " + grp.id + ")' target='_blank' href='https://www.openstreetmap.org/relation/" + grp.osmid + "'>" + grp.osmid + "</a>";
170
+    }
159 171
 
160
-    var tbody = document.createElement('tbody');
161
-    attrTbl.appendChild(tbody);
172
+    content.appendChild(newMembers);
173
+    if (grp.osmid != 1) content.appendChild(oldMembers);
162 174
 
163 175
     for (var key in grp.stations) {
164
-        //var row = document.createElement('tr');
165
-        //var col1 = document.createElement('td');
166
-        //var col2 = document.createElement('td');
167
-        //tbody.appendChild(row);
168
-        //row.appendChild(col1);
169
-        //row.appendChild(col2);
170
-        //col1.innerHTML = "<a href=\"https://wiki.openstreetmap.org/wiki/Key:" + key + "\" target=\"_blank\">" + key + "</a>";
171
-        //for (var i = 0; i <  stat.attrs[key].length; i++) col2.innerHTML += "<span class='attrval'>" + stat.attrs[key][i] + "</span>"+ "<br>";
172
-        //attrrows[key] = row;
176
+        var stat = grp.stations[key];
177
+        var row = document.createElement('div');
178
+
179
+        if (stat.attrs.name) {        
180
+            row.innerHTML = "Node <a onmouseover='nodeHl( " + stat.id + ")' onmouseout='nodeUnHl( " + stat.id + ")' target='_blank' href='https://www.openstreetmap.org/node/" + stat.osmid + "'>" + stat.osmid + "</a> (<b>\"" + stat.attrs.name + "\"</b>)";
181
+        } else {
182
+            row.innerHTML = "Node <a onmouseover='nodeHl( " + stat.id + ")' onmouseout='nodeUnHl( " + stat.id + ")' target='_blank' href='https://www.openstreetmap.org/node/" + stat.osmid + "'>" + stat.osmid + "</a>";
183
+        }
184
+
185
+        if (grp.osmid == 1 || stat.orig_group != grp.id) newMembers.appendChild(row);
186
+        else oldMembers.appendChild(row);
173 187
     }
174 188
 
175 189
     if (map.getZoom() < 18) {
@@ -245,7 +259,6 @@ map.on("moveend", function () {
245 259
     render();
246 260
 });
247 261
 
248
-
249 262
 function render() {
250 263
     var xmlhttp = new XMLHttpRequest();
251 264
 
@@ -257,19 +270,17 @@ function render() {
257 270
                 layer.clearLayers();
258 271
                 labelLayer.clearLayers();
259 272
 
260
-                var blur = 21 - map.getZoom();
261
-                var rad = 21 - map.getZoom();
273
+                var blur = 22 - map.getZoom();
274
+                var rad = 25 - map.getZoom();
262 275
 
263
-                blur = Math.max(5, Math.min(9, blur));
264
-                rad = Math.min(10, rad);
265
-
266
-                layer.addLayer(L.heatLayer(content.ok, {gradient: {0: '#cbf7cb', 0.5: '#78f378', 1: '#29c329'}, minOpacity: 1, blur: blur, radius: rad, maxZoom: 15}));
267
-                layer.addLayer(L.heatLayer(content.sugg, {gradient: {0: '#7f7fbd', 0.5: '#4444b3', 1: '#0606c1'}, minOpacity: 1, blur: blur-3, radius: rad-3, maxZoom: 15}));
268
-                layer.addLayer(L.heatLayer(content.err, {gradient: {0: '#f39191', 0.5: '#ff5656', 1: '#ff0000'}, minOpacity: 1, blur: blur-3, radius: rad-3, maxZoom: 15}));
276
+                layer.addLayer(L.heatLayer(content.ok, {max: 500, gradient: {0: '#cbf7cb', 0.5: '#78f378', 1: '#29c329'}, minOpacity: 0.65, blur: blur, radius: rad}));
277
+                layer.addLayer(L.heatLayer(content.sugg, {max: 500, gradient: {0: '#7f7fbd', 0.5: '#4444b3', 1: '#0606c1'}, minOpacity: 0.65, blur: blur-3, radius: rad-4}));
278
+                
279
+                layer.addLayer(L.heatLayer(content.err, {max: 500, gradient: {0: '#f39191', 0.5: '#ff5656', 1: '#ff0000'}, minOpacity: 0.75, blur: blur-3, radius: rad-3, maxZoom: 15}));
269 280
             }
270 281
         };
271 282
 
272
-        xmlhttp.open("GET", "http://localhost:9090/heatmap?bbox=" + [map.getBounds().getSouthWest().lat, map.getBounds().getSouthWest().lng, map.getBounds().getNorthEast().lat, map.getBounds().getNorthEast().lng].join(","), true);
283
+        xmlhttp.open("GET", "http://localhost:9090/heatmap?z=" + map.getZoom() + "&bbox=" + [map.getBounds().getSouthWest().lat, map.getBounds().getSouthWest().lng, map.getBounds().getNorthEast().lat, map.getBounds().getNorthEast().lng].join(","), true);
273 284
         xmlhttp.send();
274 285
     } else {
275 286
         xmlhttp.onreadystatechange = function() {
@@ -299,8 +310,8 @@ function render() {
299 310
                 }
300 311
 
301 312
                 var suggs = [];
302
-                for (var i = 0; i < content.suggestions.length; i++) {
303
-                    sugg = content.suggestions[i];
313
+                for (var i = 0; i < content.su.length; i++) {
314
+                    sugg = content.su[i];
304 315
                     var a = sugArr(sugg, map.getZoom());
305 316
                     suggs.push(a[0]);                    
306 317
                     suggs.push(a[1]);
@@ -308,10 +319,13 @@ function render() {
308 319
 
309 320
                 if (map.getZoom() > 15) {
310 321
                    labelLayer.addLayer(L.featureGroup(labels));
311
-                   layer.addLayer(L.featureGroup(groups).on('click', function(a) { openGroup(a.layer.options.id, a.layer.getBounds().getCenter());}));
312 322
                    layer.addLayer(L.featureGroup(suggs).on('click', function(a) { openStat(a.layer.options.id, a.layer.getBounds().getCenter());}));
313 323
                 }
314 324
 
325
+                if (map.getZoom() > 13) {
326
+                   layer.addLayer(L.featureGroup(groups).on('click', function(a) { openGroup(a.layer.options.id, a.layer.getBounds().getCenter());}));
327
+                }
328
+
315 329
 
316 330
                 layer.addLayer(L.featureGroup(stations).on('click', function(a) {
317 331
                     openStat(a.layer.options.id, a.layer.getBounds().getCenter());}));