1 /*
2    This file is part of Qadastre.
3    Copyright (C)  2010 Pierre Ducroquet <pinaraf@pinaraf.info>
4 
5    Qadastre is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation, either version 3 of the License, or
8    (at your option) any later version.
9 
10    Qadastre is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with Qadastre. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 #include "cadastrewrapper.h"
20 #include <QUrl>
21 #include <QDebug>
22 #include <QNetworkReply>
23 #include <QNetworkRequest>
24 #include <QWebPage>
25 #include <QWebFrame>
26 #include <QWebElement>
27 #include <QDesktopServices>
28 #include <QDir>
29 #include <QFile>
30 #include <QApplication>
31 #include <QList>
32 #include <QSettings>
33 #include <QImage>
34 #include <QColor>
35 
36 CadastreWrapper *CadastreWrapper::m_instance = 0;
37 
instance()38 CadastreWrapper *CadastreWrapper::instance()
39 {
40     if (!CadastreWrapper::m_instance)
41         CadastreWrapper::m_instance = new CadastreWrapper;
42     return CadastreWrapper::m_instance;
43 }
44 
CadastreWrapper(QObject * parent)45 CadastreWrapper::CadastreWrapper(QObject *parent) :
46     QObject(parent)
47     , m_networkManager(0)
48     , m_gotCookie(false)
49 {
50     setRootCacheDir(QDesktopServices::storageLocation(QDesktopServices::DataLocation));
51 }
52 
setNetworkManager(QNetworkAccessManager * aManager)53 void CadastreWrapper::setNetworkManager(QNetworkAccessManager *aManager)
54 {
55     if (m_networkManager)
56         disconnect(m_networkManager, 0, this, 0);
57     m_networkManager = aManager;
58     connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkFinished(QNetworkReply*)));
59     m_networkManager->get(QNetworkRequest(QUrl("http://www.cadastre.gouv.fr/scpc/accueil.do")));
60 }
61 
searchVille(const QString & city,const QString & department)62 void CadastreWrapper::searchVille(const QString &city, const QString &department)
63 {
64     // {"numerovoie": "", "indiceRepetition": "", "nomvoie": "", "lieuDit": "", "ville": city.upper(), "codePostal": "", "codeDepartement": dept, "nbResultatParPage": 20, "x": 0, "y" : 0}
65     QString data = QString("numerovoie=&indiceRepetition=&nomvoie=&lieuDit=&ville=%1&codePostal=&codeDepartement=%2&nbResultatParPage=20&x=0&y=0")
66                    .arg(QString::fromLatin1(QUrl::toPercentEncoding(city.toUpper())))
67                    .arg(department);
68     qDebug() << data;
69     qDebug() << data.toLatin1();
70     qDebug() << m_networkManager;
71     m_networkManager->post(QNetworkRequest(QUrl("http://www.cadastre.gouv.fr/scpc/rechercherPlan.do")), data.toLatin1());
72 }
73 
searchCode(const QString & code,const QString & department)74 void CadastreWrapper::searchCode(const QString &code, const QString &department)
75 {
76     // {"numerovoie": "", "indiceRepetition": "", "nomvoie": "", "lieuDit": "", "ville": city.upper(), "codePostal": "", "codeDepartement": dept, "nbResultatParPage": 20, "x": 0, "y" : 0}
77     QString data = QString("numerovoie=&indiceRepetition=&nomvoie=&lieuDit=&codeCommune=%1&codeDepartement=%2&nbResultatParPage=20&x=0&y=0")
78                    .arg(QString::fromLatin1(QUrl::toPercentEncoding(code.toUpper())))
79                    .arg(department);
80     qDebug() << data;
81     qDebug() << data.toLatin1();
82     qDebug() << m_networkManager;
83     m_networkManager->post(QNetworkRequest(QUrl("http://www.cadastre.gouv.fr/scpc/rechercherPlan.do")), data.toLatin1());
84 }
85 
requestCity(const QString & code)86 City CadastreWrapper::requestCity(const QString &code)
87 {
88     qDebug() << "Request city: " << code;
89     QDir cache = m_cacheDir;
90     QNetworkReply *reply = m_networkManager->get(QNetworkRequest(QUrl("http://www.cadastre.gouv.fr/scpc/afficherCarteCommune.do?c=" + code)));
91     while (!reply->isFinished())
92         qApp->processEvents();
93     cache.cd(code);
94     QSettings raw_city(cache.absoluteFilePath("cache.ini"), QSettings::IniFormat);
95     City result(code);
96     result.setName(raw_city.value("name").toString());
97     result.setDepartement(raw_city.value("department").toString());
98     result.setGeometry(raw_city.value("geometry").toRect());
99     result.setProjection(raw_city.value("projection").toString());
100     return result;
101 }
102 
tileFile(const QString & code,int row,int column)103 QString CadastreWrapper::tileFile(const QString &code, int row, int column)
104 {
105     QDir cache = m_cacheDir;
106     cache.cd(code);
107     QString fileName = QString("%1-%2.png").arg(row).arg(column);
108     return cache.absoluteFilePath(fileName);
109 }
110 
downloadTiles(City city)111 bool CadastreWrapper::downloadTiles(City city)
112 {
113     m_progress = new QProgressDialog();
114     m_progress->setWindowTitle(QApplication::tr("Downloading tiles..."));
115     m_progress->setMaximum(city.tileRows() * city.tileColumns());
116     m_progress->setMinimum(0);
117     m_progress->setValue(0);
118     m_progress->show();
119 
120     QDir cache = m_cacheDir;
121     cache.cd(city.code());
122 
123     for (int r = 0 ; r < city.tileRows() ; ++r) {
124         for (int c = 0 ; c < city.tileColumns() ; ++c) {
125             QString fileName = QString("%1-%2.png").arg(r).arg(c);
126             qDebug() << fileName;
127             if (cache.exists(fileName) && QFileInfo(cache, fileName).size()) {
128                 // the file already exists, cool !
129             } else {
130                 QRect rect = city.tileGeometry(r, c);
131                 QString bbox = QString("%1.0,%2.0,%3.0,%4.0").arg(rect.left()).arg(rect.bottom()).arg(rect.right()).arg(rect.top());
132                 fileName = cache.absoluteFilePath(fileName);
133                 m_waitingTiles[fileName] = city.tileGeometry(r, c);
134             }
135         }
136     }
137     m_progress->setMaximum(m_waitingTiles.count());
138     m_startTime = QDateTime::currentDateTime();
139     while (m_waitingTiles.count() > 0 && !m_progress->wasCanceled()) {
140         QString fileName = m_waitingTiles.begin().key();
141         QRect rect = m_waitingTiles.begin().value();
142         m_waitingTiles.take(fileName);
143         QString bbox = QString("%1.0,%2.0,%3.0,%4.0").arg(rect.left()).arg(rect.top()).arg(rect.right()).arg(rect.bottom());
144         QString url = QString("http://www.cadastre.gouv.fr/scpc/wms?version=1.1&request=GetMap&layers=CDIF:LS3,CDIF:LS2,CDIF:LS1,CDIF:PARCELLE,CDIF:NUMERO,CDIF:PT3,CDIF:PT2,CDIF:PT1,CDIF:LIEUDIT,CDIF:COMMUNE&format=image/png&bbox=%1&width=600&height=600&exception=application/vnd.ogc.se_inimage&styles=LS3_90,LS2_90,LS1_90,PARCELLE_90,NUMERO_90,PT3_90,PT2_90,PT1_90,LIEUDIT_90,COMMUNE_90").arg(bbox);
145         qDebug() << url;
146         m_pendingTiles[m_networkManager->get(QNetworkRequest(QUrl(url)))] = fileName;
147         while (m_pendingTiles.count() > 0 && !m_progress->wasCanceled()) {
148             qApp->processEvents();
149         }
150     }
151 
152     bool ret = true;
153     if (m_progress->wasCanceled())
154         ret = false;
155     else {
156         QSettings settings(cache.absoluteFilePath("cache.ini"), QSettings::IniFormat);
157         settings.setValue("complete", true);
158         settings.sync();
159     }
160 
161     m_progress->hide();
162     m_progress->deleteLater();
163     m_progress = NULL;
164 
165     return ret;
166 }
167 
networkFinished(QNetworkReply * reply)168 void CadastreWrapper::networkFinished(QNetworkReply *reply)
169 {
170     if (m_pendingTiles.contains(reply)) {
171         QFile target(m_pendingTiles[reply]);
172         QByteArray ba = reply->readAll();
173 
174         // white -> transparent
175         QImage img;
176         img.loadFromData(ba);
177         QImage img2 = img.convertToFormat(QImage::Format_ARGB32);
178         Q_ASSERT(img2.hasAlphaChannel());
179         int w=0;
180         for (int y=0; y<img2.height(); ++y) {
181             for (int x=0; x<img2.width(); ++x) {
182                 QColor col = QColor(img2.pixel(x, y));
183                 if (col == QColor(255, 255, 255)) {
184                     col.setAlpha(0);
185                     img2.setPixel(x, y, col.rgba());
186                     ++w;
187                }
188             }
189         }
190         //Full transparent
191         if (w == img2.height()*img2.width()) {
192             img2 = QImage(1, 1, QImage::Format_ARGB32);
193             img2.setPixel(0, 0, QColor(0, 0, 0, 0).rgba());
194         }
195 
196         target.open(QIODevice::WriteOnly);
197 //        target.write(reply->readAll());
198         img2.save(&target, "PNG");
199         target.close();
200         m_pendingTiles.remove(reply);
201         if (m_progress) {
202             m_progress->setValue(m_progress->value()+1);
203             if (m_progress->value() > 10) {
204                 double ms = m_startTime.secsTo(QDateTime::currentDateTime());
205                 double us = ms/m_progress->value();
206                 int tot = us*(m_progress->maximum() - m_progress->value());
207 
208                 if (tot<3600)
209                     m_progress->setLabelText(tr("Downloaded: %1/%2\nRemaining time: %3:%4").arg(m_progress->value()).arg(m_progress->maximum()).arg(int(tot/60)).arg(int(tot%60), 2, 10, QChar('0')));
210                 else
211                     m_progress->setLabelText(tr("Downloaded: %1/%2\nRemaining time: %3:%4:%5").arg(m_progress->value()).arg(m_progress->maximum()).arg(int(tot/3600)).arg(int((tot%3600)/60), 2, 10, QChar('0')).arg(int(tot%60), 2, 10, QChar('0')));
212 
213             } else
214                 m_progress->setLabelText(tr("Downloaded: %1/%2").arg(m_progress->value()).arg(m_progress->maximum()));
215         }
216     } else if (reply->url() == QUrl("http://www.cadastre.gouv.fr/scpc/accueil.do")) {
217         qDebug() << "Ok, I've got a cookie... I LOVE COOKIES.";
218         reply->readAll();
219         m_gotCookie = true;
220     } else if (reply->url() == QUrl("http://www.cadastre.gouv.fr/scpc/rechercherPlan.do")) {
221         QString pageData = reply->readAll();
222         qDebug() << pageData;
223         QWebPage parsedPage(this);
224         QWebFrame *frame = parsedPage.mainFrame();
225         frame->setHtml(pageData);
226         QWebElement codeCommune = frame->findFirstElement("#codeCommune");
227         QMap<QString, QString> results;
228         if (!codeCommune.isNull()) {
229             // If there is a codeCommune object in the DOM, it means that the search was not successfull.
230             if (codeCommune.tagName().toLower() != "select") {
231                 qDebug() << "Invalid page ???";
232                 return;
233             }
234             QWebElementCollection options = codeCommune.findAll("option");
235             foreach (QWebElement option, options) {
236                 if (!option.attribute("value").isEmpty())
237                     results[option.attribute("value")] = option.toPlainText();
238             }
239         } else {
240             // We may have been successfull, who knows ?
241             QString name = frame->findFirstElement("#ville").attribute("value");
242             QWebElementCollection links = frame->findAllElements(".resultat > .parcelles a");
243             QRegExp linkRE("c=(\\w+)");
244             foreach (QWebElement link, links) {
245                 QString js = link.attribute("onclick");
246                 int pos = linkRE.indexIn(js);
247                 if (pos > -1) {
248                     results[linkRE.cap(1)] = name;
249                 }
250             }
251         }
252         qDebug() << results;
253         emit(resultsAvailable(results));
254     } else if (reply->url().toString().startsWith("http://www.cadastre.gouv.fr/scpc/afficherCarteCommune.do?c=")) {
255         qDebug() << "Got a result !";
256         QString pageData = reply->readAll();
257         if (pageData.isEmpty())
258             return;
259 //        qDebug() << pageData;
260         QString name, code, projection;
261         code = reply->url().queryItemValue("c");
262         qDebug() << code;
263         bool inGeoBox = false;
264         QList<int> raw_geometry;
265         foreach (QString line, pageData.split('\n')) {
266             line = line.trimmed();
267             if (name.isEmpty()) {
268                 if (line.contains("<title>"))
269                     if (line.split(" : ").count() > 1)
270                         name = line.split(" : ")[1].split(" - ")[0];
271             }
272             if (projection.isEmpty()) {
273                 if (line.contains("projectionName")) {
274                     QRegExp reg("<span id=\"projectionName\">(.+)</span>");
275                     reg.setMinimal(true);
276                     if (reg.indexIn(line) > -1) {
277                         projection = reg.cap(1);
278                         qDebug() << projection;
279                         if (projection.compare("RGF93CC42", Qt::CaseInsensitive) == 0)
280                             projection = "+title=Projection conique conforme Zone 1 +proj=lcc +towgs84=0.0000,0.0000,0.0000 +a=6378137.0000 +rf=298.2572221010000 +lat_0=42.000000000 +lon_0=3.000000000 +lat_1=41.250000000 +lat_2=42.750000000 +x_0=1700000.000 +y_0=1200000.000 +units=m +no_defs";
281                         else if (projection.compare("RGF93CC43", Qt::CaseInsensitive) == 0)
282                             projection = "+title=Projection conique conforme Zone 2 +proj=lcc +towgs84=0.0000,0.0000,0.0000 +a=6378137.0000 +rf=298.2572221010000 +lat_0=43.000000000 +lon_0=3.000000000 +lat_1=42.250000000 +lat_2=43.750000000 +x_0=1700000.000 +y_0=2200000.000 +units=m +no_defs";
283                         else if (projection.compare("RGF93CC44", Qt::CaseInsensitive) == 0)
284                             projection = "+title=Projection conique conforme Zone 3 +proj=lcc +towgs84=0.0000,0.0000,0.0000 +a=6378137.0000 +rf=298.2572221010000 +lat_0=44.000000000 +lon_0=3.000000000 +lat_1=43.250000000 +lat_2=44.750000000 +x_0=1700000.000 +y_0=3200000.000 +units=m +no_defs";
285                         else if (projection.compare("RGF93CC45", Qt::CaseInsensitive) == 0)
286                             projection = "+title=Projection conique conforme Zone 4 +proj=lcc +towgs84=0.0000,0.0000,0.0000 +a=6378137.0000 +rf=298.2572221010000 +lat_0=45.000000000 +lon_0=3.000000000 +lat_1=44.250000000 +lat_2=45.750000000 +x_0=1700000.000 +y_0=4200000.000 +units=m +no_defs";
287                         else if (projection.compare("RGF93CC46", Qt::CaseInsensitive) == 0)
288                             projection = "+title=Projection conique conforme Zone 5 +proj=lcc +towgs84=0.0000,0.0000,0.0000 +a=6378137.0000 +rf=298.2572221010000 +lat_0=46.000000000 +lon_0=3.000000000 +lat_1=45.250000000 +lat_2=46.750000000 +x_0=1700000.000 +y_0=5200000.000 +units=m +no_defs";
289                         else if (projection.compare("RGF93CC47", Qt::CaseInsensitive) == 0)
290                             projection = "+title=Projection conique conforme Zone 6 +proj=lcc +towgs84=0.0000,0.0000,0.0000 +a=6378137.0000 +rf=298.2572221010000 +lat_0=47.000000000 +lon_0=3.000000000 +lat_1=46.250000000 +lat_2=47.750000000 +x_0=1700000.000 +y_0=6200000.000 +units=m +no_defs";
291                         else if (projection.compare("RGF93CC48", Qt::CaseInsensitive) == 0)
292                             projection = "+title=Projection conique conforme Zone 7 +proj=lcc +towgs84=0.0000,0.0000,0.0000 +a=6378137.0000 +rf=298.2572221010000 +lat_0=48.000000000 +lon_0=3.000000000 +lat_1=47.250000000 +lat_2=48.750000000 +x_0=1700000.000 +y_0=7200000.000 +units=m +no_defs";
293                         else if (projection.compare("RGF93CC49", Qt::CaseInsensitive) == 0)
294                             projection = "+title=Projection conique conforme Zone 8 +proj=lcc +towgs84=0.0000,0.0000,0.0000 +a=6378137.0000 +rf=298.2572221010000 +lat_0=49.000000000 +lon_0=3.000000000 +lat_1=48.250000000 +lat_2=49.750000000 +x_0=1700000.000 +y_0=8200000.000 +units=m +no_defs";
295                         else if (projection.compare("RGF93CC50", Qt::CaseInsensitive) == 0)
296                             projection = "+title=Projection conique conforme Zone 9 +proj=lcc +towgs84=0.0000,0.0000,0.0000 +a=6378137.0000 +rf=298.2572221010000 +lat_0=50.000000000 +lon_0=3.000000000 +lat_1=49.250000000 +lat_2=50.750000000 +x_0=1700000.000 +y_0=9200000.000 +units=m +no_defs";
297                         else if (projection.compare("LAMB1", Qt::CaseInsensitive) == 0)
298                             projection = "+title=Lambert I +proj=lcc +nadgrids=ntf_r93.gsb,null +towgs84=-168.0000,-60.0000,320.0000 +a=6378249.2000 +rf=293.4660210000000 +pm=2.337229167 +lat_0=49.500000000 +lon_0=0.000000000 +k_0=0.99987734 +lat_1=49.500000000 +x_0=600000.000 +y_0=200000.000 +units=m +no_defs";
299                         else if (projection.compare("LAMB2", Qt::CaseInsensitive) == 0)
300                             projection = "+title=Lambert II +proj=lcc +nadgrids=ntf_r93.gsb,null +towgs84=-168.0000,-60.0000,320.0000 +a=6378249.2000 +rf=293.4660210000000 +pm=2.337229167 +lat_0=46.800000000 +lon_0=0.000000000 +k_0=0.99987742 +lat_1=46.800000000 +x_0=600000.000 +y_0=200000.000 +units=m +no_defs";
301                         else if (projection.compare("LAMB3", Qt::CaseInsensitive) == 0)
302                             projection = "+title=Lambert III +proj=lcc +nadgrids=ntf_r93.gsb,null +towgs84=-168.0000,-60.0000,320.0000 +a=6378249.2000 +rf=293.4660210000000 +pm=2.337229167 +lat_0=44.100000000 +lon_0=0.000000000 +k_0=0.99987750 +lat_1=44.100000000 +x_0=600000.000 +y_0=200000.000 +units=m +no_defs";
303                         else if (projection.compare("LAMB4", Qt::CaseInsensitive) == 0)
304                             projection = "+title=Lambert IV +proj=lcc +nadgrids=ntf_r93.gsb,null +towgs84=-168.0000,-60.0000,320.0000 +a=6378249.2000 +rf=293.4660210000000 +pm=2.337229167 +lat_0=42.165000000 +lon_0=0.000000000 +k_0=0.99994471 +lat_1=42.165000000 +x_0=234.358 +y_0=185861.369 +units=m +no_defs";
305                     }
306                 }
307             }
308             if (inGeoBox) {
309                 raw_geometry.append(line.split(".")[0].split(")")[0].toInt());
310                 if (line.contains(')'))
311                     inGeoBox = false;
312             }
313             if (line == "new GeoBox(")
314                 inGeoBox = true;
315         }
316         if (!raw_geometry.size())
317             return;
318         qDebug() << raw_geometry;
319         QRect geometry(raw_geometry[0], raw_geometry[1], raw_geometry[2]-raw_geometry[0], raw_geometry[3]-raw_geometry[1]);
320         qDebug() << geometry;
321         QDir cache = m_cacheDir;
322         if (!cache.exists(code))
323             cache.mkdir(code);
324         cache.cd(code);
325         qDebug() << cache.absoluteFilePath("cache.ini");
326         QSettings settings(cache.absoluteFilePath("cache.ini"), QSettings::IniFormat);
327         settings.setValue("name", name);
328         settings.setValue("geometry", geometry);
329         settings.setValue("department", name.mid(name.lastIndexOf('(')+1, name.lastIndexOf(')')-name.lastIndexOf('(')-1));
330         settings.setValue("projection", projection);
331         settings.sync();
332     }
333     reply->close();
334 }
335 
setRootCacheDir(QDir dir)336 void CadastreWrapper::setRootCacheDir(QDir dir)
337 {
338     m_cacheDir = dir;
339     if (!m_cacheDir.cd("qadastre")) {
340         m_cacheDir.mkdir("qadastre");
341         m_cacheDir.cd("qadastre");
342     }
343 }
344 
getCacheDir()345 QDir CadastreWrapper::getCacheDir()
346 {
347     return m_cacheDir;
348 }
349