Revision control

++ // Copyright (C) 2017 Mapbox, Inc.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #include "qmapboxglstylechange_p.h"
++
++ #include <QtCore/QDebug>
++ #include <QtCore/QMetaProperty>
++ #include <QtCore/QRegularExpression>
++ #include <QtCore/QStringList>
++ #include <QtPositioning/QGeoPath>
++ #include <QtPositioning/QGeoPolygon>
++ #include <QtQml/QJSValue>
++ #include <QtLocation/private/qdeclarativecirclemapitem_p_p.h>
++
++ namespace {
++
++ QByteArray formatPropertyName(const QByteArray &name)
++ {
++ QString nameAsString = QString::fromLatin1(name);
++ static const QRegularExpression camelCaseRegex(QStringLiteral("([a-z0-9])([A-Z])"));
++ return nameAsString.replace(camelCaseRegex, QStringLiteral("\\1-\\2")).toLower().toLatin1();
++ }
++
++ bool isImmutableProperty(const QByteArray &name)
++ {
++ return name == QStringLiteral("type") || name == QStringLiteral("layer");
++ }
++
++ QString getId(QDeclarativeGeoMapItemBase *mapItem)
++ {
++ return QStringLiteral("QtLocation-") +
++ ((mapItem->objectName().isEmpty()) ? QString::number(quint64(mapItem)) : mapItem->objectName());
++ }
++
++ // Mapbox GL supports geometry segments that spans above 180 degrees in
++ // longitude. To keep visual expectations in parity with Qt, we need to adapt
++ // the coordinates to always use the shortest path when in ambiguity.
++ static bool geoRectangleCrossesDateLine(const QGeoRectangle &rect) {
++ return rect.topLeft().longitude() > rect.bottomRight().longitude();
++ }
++
++ QMapbox::Feature featureFromMapRectangle(QDeclarativeRectangleMapItem *mapItem)
++ {
++ const QGeoRectangle *rect = static_cast<const QGeoRectangle *>(&mapItem->geoShape());
++ QMapbox::Coordinate bottomLeft { rect->bottomLeft().latitude(), rect->bottomLeft().longitude() };
++ QMapbox::Coordinate topLeft { rect->topLeft().latitude(), rect->topLeft().longitude() };
++ QMapbox::Coordinate bottomRight { rect->bottomRight().latitude(), rect->bottomRight().longitude() };
++ QMapbox::Coordinate topRight { rect->topRight().latitude(), rect->topRight().longitude() };
++ if (geoRectangleCrossesDateLine(*rect)) {
++ bottomRight.second += 360.0;
++ topRight.second += 360.0;
++ }
++ QMapbox::CoordinatesCollections geometry { { { bottomLeft, bottomRight, topRight, topLeft, bottomLeft } } };
++
++ return QMapbox::Feature(QMapbox::Feature::PolygonType, geometry, {}, getId(mapItem));
++ }
++
++ QMapbox::Feature featureFromMapCircle(QDeclarativeCircleMapItem *mapItem)
++ {
++ static const int circleSamples = 128;
++ const QGeoProjectionWebMercator &p = static_cast<const QGeoProjectionWebMercator&>(mapItem->map()->geoProjection());
++ QList<QGeoCoordinate> path;
++ QGeoCoordinate leftBound;
++ QDeclarativeCircleMapItemPrivate::calculatePeripheralPoints(path, mapItem->center(), mapItem->radius(), circleSamples, leftBound);
++ QList<QDoubleVector2D> pathProjected;
++ for (const QGeoCoordinate &c : std::as_const(path))
++ pathProjected << p.geoToMapProjection(c);
++ if (QDeclarativeCircleMapItemPrivateCPU::crossEarthPole(mapItem->center(), mapItem->radius()))
++ QDeclarativeCircleMapItemPrivateCPU::preserveCircleGeometry(pathProjected, mapItem->center(), mapItem->radius(), p);
++ path.clear();
++ for (const QDoubleVector2D &c : std::as_const(pathProjected))
++ path << p.mapProjectionToGeo(c);
++
++
++ QMapbox::Coordinates coordinates;
++ for (const QGeoCoordinate &coordinate : path) {
++ coordinates << QMapbox::Coordinate { coordinate.latitude(), coordinate.longitude() };
++ }
++ coordinates.append(coordinates.first()); // closing the path
++ QMapbox::CoordinatesCollections geometry { { coordinates } };
++ return QMapbox::Feature(QMapbox::Feature::PolygonType, geometry, {}, getId(mapItem));
++ }
++
++ static QMapbox::Coordinates qgeocoordinate2mapboxcoordinate(const QList<QGeoCoordinate> &crds, const bool crossesDateline, bool closed = false)
++ {
++ QMapbox::Coordinates coordinates;
++ for (const QGeoCoordinate &coordinate : crds) {
++ if (!coordinates.empty() && crossesDateline && qAbs(coordinate.longitude() - coordinates.last().second) > 180.0) {
++ coordinates << QMapbox::Coordinate { coordinate.latitude(), coordinate.longitude() + (coordinate.longitude() >= 0 ? -360.0 : 360.0) };
++ } else {
++ coordinates << QMapbox::Coordinate { coordinate.latitude(), coordinate.longitude() };
++ }
++ }
++ if (closed && !coordinates.empty() && coordinates.last() != coordinates.first())
++ coordinates.append(coordinates.first()); // closing the path
++ return coordinates;
++ }
++
++ QMapbox::Feature featureFromMapPolygon(QDeclarativePolygonMapItem *mapItem)
++ {
++ const QGeoPolygon *polygon = static_cast<const QGeoPolygon *>(&mapItem->geoShape());
++ const bool crossesDateline = geoRectangleCrossesDateLine(polygon->boundingGeoRectangle());
++ QMapbox::CoordinatesCollections geometry;
++ QMapbox::CoordinatesCollection poly;
++ QMapbox::Coordinates coordinates = qgeocoordinate2mapboxcoordinate(polygon->perimeter(), crossesDateline, true);
++ poly.push_back(coordinates);
++ for (int i = 0; i < polygon->holesCount(); ++i) {
++ coordinates = qgeocoordinate2mapboxcoordinate(polygon->holePath(i), crossesDateline, true);
++ poly.push_back(coordinates);
++ }
++
++ geometry.push_back(poly);
++ return QMapbox::Feature(QMapbox::Feature::PolygonType, geometry, {}, getId(mapItem));
++ }
++
++ QMapbox::Feature featureFromMapPolyline(QDeclarativePolylineMapItem *mapItem)
++ {
++ const QGeoPath *path = static_cast<const QGeoPath *>(&mapItem->geoShape());
++ QMapbox::Coordinates coordinates;
++ const bool crossesDateline = geoRectangleCrossesDateLine(path->boundingGeoRectangle());
++ for (const QGeoCoordinate &coordinate : path->path()) {
++ if (!coordinates.empty() && crossesDateline && qAbs(coordinate.longitude() - coordinates.last().second) > 180.0) {
++ coordinates << QMapbox::Coordinate { coordinate.latitude(), coordinate.longitude() + (coordinate.longitude() >= 0 ? -360.0 : 360.0) };
++ } else {
++ coordinates << QMapbox::Coordinate { coordinate.latitude(), coordinate.longitude() };
++ }
++ }
++ QMapbox::CoordinatesCollections geometry { { coordinates } };
++
++ return QMapbox::Feature(QMapbox::Feature::LineStringType, geometry, {}, getId(mapItem));
++ }
++
++ QMapbox::Feature featureFromMapItem(QDeclarativeGeoMapItemBase *item)
++ {
++ switch (item->itemType()) {
++ case QGeoMap::MapRectangle:
++ return featureFromMapRectangle(static_cast<QDeclarativeRectangleMapItem *>(item));
++ case QGeoMap::MapCircle:
++ return featureFromMapCircle(static_cast<QDeclarativeCircleMapItem *>(item));
++ case QGeoMap::MapPolygon:
++ return featureFromMapPolygon(static_cast<QDeclarativePolygonMapItem *>(item));
++ case QGeoMap::MapPolyline:
++ return featureFromMapPolyline(static_cast<QDeclarativePolylineMapItem *>(item));
++ default:
++ qWarning() << "Unsupported QGeoMap item type: " << item->itemType();
++ return QMapbox::Feature();
++ }
++ }
++
++ QList<QByteArray> getAllPropertyNamesList(QObject *object)
++ {
++ const QMetaObject *metaObject = object->metaObject();
++ QList<QByteArray> propertyNames(object->dynamicPropertyNames());
++ for (int i = metaObject->propertyOffset(); i < metaObject->propertyCount(); ++i) {
++ propertyNames.append(metaObject->property(i).name());
++ }
++ return propertyNames;
++ }
++
++ } // namespace
++
++
++ QList<QSharedPointer<QMapboxGLStyleChange>> QMapboxGLStyleChange::addMapItem(QDeclarativeGeoMapItemBase *item, const QString &before)
++ {
++ QList<QSharedPointer<QMapboxGLStyleChange>> changes;
++
++ switch (item->itemType()) {
++ case QGeoMap::MapRectangle:
++ case QGeoMap::MapCircle:
++ case QGeoMap::MapPolygon:
++ case QGeoMap::MapPolyline:
++ break;
++ default:
++ qWarning() << "Unsupported QGeoMap item type: " << item->itemType();
++ return changes;
++ }
++
++ QMapbox::Feature feature = featureFromMapItem(item);
++
++ changes << QMapboxGLStyleAddLayer::fromFeature(feature, before);
++ changes << QMapboxGLStyleAddSource::fromFeature(feature);
++ changes << QMapboxGLStyleSetPaintProperty::fromMapItem(item);
++ changes << QMapboxGLStyleSetLayoutProperty::fromMapItem(item);
++
++ return changes;
++ }
++
++ QList<QSharedPointer<QMapboxGLStyleChange>> QMapboxGLStyleChange::removeMapItem(QDeclarativeGeoMapItemBase *item)
++ {
++ QList<QSharedPointer<QMapboxGLStyleChange>> changes;
++
++ const QString id = getId(item);
++
++ changes << QSharedPointer<QMapboxGLStyleChange>(new QMapboxGLStyleRemoveLayer(id));
++ changes << QSharedPointer<QMapboxGLStyleChange>(new QMapboxGLStyleRemoveSource(id));
++
++ return changes;
++ }
++
++ // QMapboxGLStyleSetLayoutProperty
++
++ void QMapboxGLStyleSetLayoutProperty::apply(QMapboxGL *map)
++ {
++ map->setLayoutProperty(m_layer, m_property, m_value);
++ }
++
++ QList<QSharedPointer<QMapboxGLStyleChange>> QMapboxGLStyleSetLayoutProperty::fromMapItem(QDeclarativeGeoMapItemBase *item)
++ {
++ QList<QSharedPointer<QMapboxGLStyleChange>> changes;
++
++ switch (item->itemType()) {
++ case QGeoMap::MapPolyline:
++ changes = fromMapItem(static_cast<QDeclarativePolylineMapItem *>(item));
++ default:
++ break;
++ }
++
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetLayoutProperty(getId(item), QStringLiteral("visibility"),
++ item->isVisible() ? QStringLiteral("visible") : QStringLiteral("none")));
++
++ return changes;
++ }
++
++ QList<QSharedPointer<QMapboxGLStyleChange>> QMapboxGLStyleSetLayoutProperty::fromMapItem(QDeclarativePolylineMapItem *item)
++ {
++ QList<QSharedPointer<QMapboxGLStyleChange>> changes;
++ changes.reserve(2);
++
++ const QString id = getId(item);
++
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetLayoutProperty(id, QStringLiteral("line-cap"), QStringLiteral("square")));
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetLayoutProperty(id, QStringLiteral("line-join"), QStringLiteral("bevel")));
++
++ return changes;
++ }
++
++ QMapboxGLStyleSetLayoutProperty::QMapboxGLStyleSetLayoutProperty(const QString& layer, const QString& property, const QVariant &value)
++ : m_layer(layer), m_property(property), m_value(value)
++ {
++ }
++
++ // QMapboxGLStyleSetPaintProperty
++
++ QMapboxGLStyleSetPaintProperty::QMapboxGLStyleSetPaintProperty(const QString& layer, const QString& property, const QVariant &value)
++ : m_layer(layer), m_property(property), m_value(value)
++ {
++ }
++
++ void QMapboxGLStyleSetPaintProperty::apply(QMapboxGL *map)
++ {
++ map->setPaintProperty(m_layer, m_property, m_value);
++ }
++
++ QList<QSharedPointer<QMapboxGLStyleChange>> QMapboxGLStyleSetPaintProperty::fromMapItem(QDeclarativeGeoMapItemBase *item)
++ {
++ switch (item->itemType()) {
++ case QGeoMap::MapRectangle:
++ return fromMapItem(static_cast<QDeclarativeRectangleMapItem *>(item));
++ case QGeoMap::MapCircle:
++ return fromMapItem(static_cast<QDeclarativeCircleMapItem *>(item));
++ case QGeoMap::MapPolygon:
++ return fromMapItem(static_cast<QDeclarativePolygonMapItem *>(item));
++ case QGeoMap::MapPolyline:
++ return fromMapItem(static_cast<QDeclarativePolylineMapItem *>(item));
++ default:
++ qWarning() << "Unsupported QGeoMap item type: " << item->itemType();
++ return QList<QSharedPointer<QMapboxGLStyleChange>>();
++ }
++ }
++
++ QList<QSharedPointer<QMapboxGLStyleChange>> QMapboxGLStyleSetPaintProperty::fromMapItem(QDeclarativeRectangleMapItem *item)
++ {
++ QList<QSharedPointer<QMapboxGLStyleChange>> changes;
++ changes.reserve(3);
++
++ const QString id = getId(item);
++
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("fill-opacity"), item->color().alphaF() * item->mapItemOpacity()));
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("fill-color"), item->color()));
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("fill-outline-color"), item->border()->color()));
++
++ return changes;
++ }
++
++ QList<QSharedPointer<QMapboxGLStyleChange>> QMapboxGLStyleSetPaintProperty::fromMapItem(QDeclarativeCircleMapItem *item)
++ {
++ QList<QSharedPointer<QMapboxGLStyleChange>> changes;
++ changes.reserve(3);
++
++ const QString id = getId(item);
++
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("fill-opacity"), item->color().alphaF() * item->mapItemOpacity()));
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("fill-color"), item->color()));
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("fill-outline-color"), item->border()->color()));
++
++ return changes;
++ }
++
++ QList<QSharedPointer<QMapboxGLStyleChange>> QMapboxGLStyleSetPaintProperty::fromMapItem(QDeclarativePolygonMapItem *item)
++ {
++ QList<QSharedPointer<QMapboxGLStyleChange>> changes;
++ changes.reserve(3);
++
++ const QString id = getId(item);
++
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("fill-opacity"), item->color().alphaF() * item->mapItemOpacity()));
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("fill-color"), item->color()));
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("fill-outline-color"), item->border()->color()));
++
++ return changes;
++ }
++
++ QList<QSharedPointer<QMapboxGLStyleChange>> QMapboxGLStyleSetPaintProperty::fromMapItem(QDeclarativePolylineMapItem *item)
++ {
++ QList<QSharedPointer<QMapboxGLStyleChange>> changes;
++ changes.reserve(3);
++
++ const QString id = getId(item);
++
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("line-opacity"), item->line()->color().alphaF() * item->mapItemOpacity()));
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("line-color"), item->line()->color()));
++ changes << QSharedPointer<QMapboxGLStyleChange>(
++ new QMapboxGLStyleSetPaintProperty(id, QStringLiteral("line-width"), item->line()->width()));
++
++ return changes;
++ }
++
++ // QMapboxGLStyleAddLayer
++
++ void QMapboxGLStyleAddLayer::apply(QMapboxGL *map)
++ {
++ map->addLayer(m_params, m_before);
++ }
++
++ QSharedPointer<QMapboxGLStyleChange> QMapboxGLStyleAddLayer::fromFeature(const QMapbox::Feature &feature, const QString &before)
++ {
++ auto layer = new QMapboxGLStyleAddLayer();
++ layer->m_params[QStringLiteral("id")] = feature.id;
++ layer->m_params[QStringLiteral("source")] = feature.id;
++
++ switch (feature.type) {
++ case QMapbox::Feature::PointType:
++ layer->m_params[QStringLiteral("type")] = QStringLiteral("circle");
++ break;
++ case QMapbox::Feature::LineStringType:
++ layer->m_params[QStringLiteral("type")] = QStringLiteral("line");
++ break;
++ case QMapbox::Feature::PolygonType:
++ layer->m_params[QStringLiteral("type")] = QStringLiteral("fill");
++ break;
++ }
++
++ layer->m_before = before;
++
++ return QSharedPointer<QMapboxGLStyleChange>(layer);
++ }
++
++
++ // QMapboxGLStyleRemoveLayer
++
++ void QMapboxGLStyleRemoveLayer::apply(QMapboxGL *map)
++ {
++ map->removeLayer(m_id);
++ }
++
++ QMapboxGLStyleRemoveLayer::QMapboxGLStyleRemoveLayer(const QString &id) : m_id(id)
++ {
++ }
++
++
++ // QMapboxGLStyleAddSource
++
++ void QMapboxGLStyleAddSource::apply(QMapboxGL *map)
++ {
++ map->updateSource(m_id, m_params);
++ }
++
++ QSharedPointer<QMapboxGLStyleChange> QMapboxGLStyleAddSource::fromFeature(const QMapbox::Feature &feature)
++ {
++ auto source = new QMapboxGLStyleAddSource();
++
++ source->m_id = feature.id.toString();
++ source->m_params[QStringLiteral("type")] = QStringLiteral("geojson");
++ source->m_params[QStringLiteral("data")] = QVariant::fromValue<QMapbox::Feature>(feature);
++
++ return QSharedPointer<QMapboxGLStyleChange>(source);
++ }
++
++ QSharedPointer<QMapboxGLStyleChange> QMapboxGLStyleAddSource::fromMapItem(QDeclarativeGeoMapItemBase *item)
++ {
++ return fromFeature(featureFromMapItem(item));
++ }
++
++
++ // QMapboxGLStyleRemoveSource
++
++ void QMapboxGLStyleRemoveSource::apply(QMapboxGL *map)
++ {
++ map->removeSource(m_id);
++ }
++
++ QMapboxGLStyleRemoveSource::QMapboxGLStyleRemoveSource(const QString &id) : m_id(id)
++ {
++ }
++
++
++ // QMapboxGLStyleSetFilter
++
++ void QMapboxGLStyleSetFilter::apply(QMapboxGL *map)
++ {
++ map->setFilter(m_layer, m_filter);
++ }
++
++ // QMapboxGLStyleAddImage
++
++ void QMapboxGLStyleAddImage::apply(QMapboxGL *map)
++ {
++ map->addImage(m_name, m_sprite);
++ }