예제 #1
0
Anchors resample(const GeometryCoordinates &line, const float offset, const float spacing,
        const float angleWindowSize, const float maxAngle, const float labelLength, const bool continuedLine, const bool placeAtMiddle) {

    const float halfLabelLength = labelLength / 2.0f;
    float lineLength = 0;
    for (auto it = line.begin(), end = line.end() - 1; it != end; it++) {
        lineLength += util::dist<float>(*(it), *(it + 1));
    }

    float distance = 0;
    float markedDistance = offset - spacing;

    Anchors anchors;

    assert(spacing > 0.0);

    int i = 0;
    for (auto it = line.begin(), end = line.end() - 1; it != end; it++, i++) {
        const GeometryCoordinate &a = *(it);
        const GeometryCoordinate &b = *(it + 1);

        const float segmentDist = util::dist<float>(a, b);
        const float angle = util::angle_to(b, a);

        while (markedDistance + spacing < distance + segmentDist) {
            markedDistance += spacing;

            float t = (markedDistance - distance) / segmentDist,
                  x = util::interpolate(float(a.x), float(b.x), t),
                  y = util::interpolate(float(a.y), float(b.y), t);

            // Check that the point is within the tile boundaries and that
            // the label would fit before the beginning and end of the line
            // if placed at this point.
            if (x >= 0 && x < util::EXTENT && y >= 0 && y < util::EXTENT &&
                    markedDistance - halfLabelLength >= 0.0f &&
                    markedDistance + halfLabelLength <= lineLength) {
                Anchor anchor(::round(x), ::round(y), angle, 0.5f, i);

                if (!angleWindowSize || checkMaxAngle(line, anchor, labelLength, angleWindowSize, maxAngle)) {
                    anchors.push_back(anchor);
                }
            }
        }

        distance += segmentDist;
    }

    if (!placeAtMiddle && anchors.empty() && !continuedLine) {
        // The first attempt at finding anchors at which labels can be placed failed.
        // Try again, but this time just try placing one anchor at the middle of the line.
        // This has the most effect for short lines in overscaled tiles, since the
        // initial offset used in overscaled tiles is calculated to align labels with positions in
        // parent tiles instead of placing the label as close to the beginning as possible.
        anchors = resample(line, distance / 2, spacing, angleWindowSize, maxAngle, labelLength, continuedLine, true);
    }

    return anchors;
}
 GeometryCollection operator()(const mapbox::geometry::line_string<int16_t>& geom) const {
     GeometryCoordinates coordinates;
     coordinates.reserve(geom.size());
     for (const auto& point : geom) {
         coordinates.emplace_back(point);
     }
     return { coordinates };
 }
예제 #3
0
void getSegmentGlyphs(std::back_insert_iterator<GlyphInstances> glyphs, Anchor &anchor,
        float offset, const GeometryCoordinates &line, int segment, bool forward) {

    const bool upsideDown = !forward;

    if (offset < 0)
        forward = !forward;

    if (forward)
        segment++;

    assert((int)line.size() > segment);
    vec2<float> end = line[segment];
    vec2<float> newAnchorPoint = anchor;
    float prevscale = std::numeric_limits<float>::infinity();

    offset = std::fabs(offset);

    const float placementScale = anchor.scale;

    while (true) {
        const float dist = util::dist<float>(newAnchorPoint, end);
        const float scale = offset / dist;
        float angle = std::atan2(end.y - newAnchorPoint.y, end.x - newAnchorPoint.x);
        if (!forward)
            angle += M_PI;
        if (upsideDown)
            angle += M_PI;

        glyphs = GlyphInstance{
            /* anchor */ newAnchorPoint,
            /* offset */ static_cast<float>(upsideDown ? M_PI : 0.0),
            /* minScale */ scale,
            /* maxScale */ prevscale,
            /* angle */ static_cast<float>(std::fmod((angle + 2.0 * M_PI), (2.0 * M_PI)))};

        if (scale <= placementScale)
            break;

        newAnchorPoint = end;

        // skip duplicate nodes
        while (newAnchorPoint == end) {
            segment += forward ? 1 : -1;
            if ((int)line.size() <= segment || segment < 0) {
                anchor.scale = scale;
                return;
            }
            end = line[segment];
        }

        vec2<float> normal = util::normal<float>(newAnchorPoint, end) * dist;
        newAnchorPoint = newAnchorPoint - normal;

        prevscale = scale;
    }
}
bool polygonContainsPoint(const GeometryCoordinates& ring, const GeometryCoordinate& p) {
    bool c = false;
    for (auto i = ring.begin(), j = ring.end() - 1; i != ring.end(); j = i++) {
        auto& p1 = *i;
        auto& p2 = *j;
        if (((p1.y > p.y) != (p2.y > p.y)) && (p.x < float(p2.x - p1.x) * float(p.y - p1.y) / float(p2.y - p1.y) + p1.x)) {
            c = !c;
        }
    }
    return c;
}
예제 #5
0
std::unordered_map<std::string, std::vector<Feature>> Source::Impl::queryRenderedFeatures(const QueryParameters& parameters) const {
    std::unordered_map<std::string, std::vector<Feature>> result;
    if (renderTiles.empty() || parameters.geometry.empty()) {
        return result;
    }

    LineString<double> queryGeometry;

    for (const auto& p : parameters.geometry) {
        queryGeometry.push_back(TileCoordinate::fromScreenCoordinate(
            parameters.transformState, 0, { p.x, parameters.transformState.getSize().height - p.y }).p);
    }

    mapbox::geometry::box<double> box = mapbox::geometry::envelope(queryGeometry);


    auto sortRenderTiles = [](const RenderTile& a, const RenderTile& b) {
        return a.id.canonical.z != b.id.canonical.z ? a.id.canonical.z < b.id.canonical.z :
               a.id.canonical.y != b.id.canonical.y ? a.id.canonical.y < b.id.canonical.y :
               a.id.wrap != b.id.wrap ? a.id.wrap < b.id.wrap : a.id.canonical.x < b.id.canonical.x;
    };
    std::vector<std::reference_wrapper<const RenderTile>> sortedTiles;
    std::transform(renderTiles.cbegin(), renderTiles.cend(), std::back_inserter(sortedTiles),
                   [](const auto& pair) { return std::ref(pair.second); });
    std::sort(sortedTiles.begin(), sortedTiles.end(), sortRenderTiles);

    for (const auto& renderTileRef : sortedTiles) {
        const RenderTile& renderTile = renderTileRef.get();
        GeometryCoordinate tileSpaceBoundsMin = TileCoordinate::toGeometryCoordinate(renderTile.id, box.min);
        if (tileSpaceBoundsMin.x >= util::EXTENT || tileSpaceBoundsMin.y >= util::EXTENT) {
            continue;
        }

        GeometryCoordinate tileSpaceBoundsMax = TileCoordinate::toGeometryCoordinate(renderTile.id, box.max);
        if (tileSpaceBoundsMax.x < 0 || tileSpaceBoundsMax.y < 0) {
            continue;
        }

        GeometryCoordinates tileSpaceQueryGeometry;
        tileSpaceQueryGeometry.reserve(queryGeometry.size());
        for (const auto& c : queryGeometry) {
            tileSpaceQueryGeometry.push_back(TileCoordinate::toGeometryCoordinate(renderTile.id, c));
        }

        renderTile.tile.queryRenderedFeatures(result,
                                              tileSpaceQueryGeometry,
                                              parameters.transformState,
                                              parameters.layerIDs);
    }

    return result;
}
 GeometryCollection operator()(const mapbox::geometry::multi_line_string<int16_t>& geom) const {
     GeometryCollection collection;
     collection.reserve(geom.size());
     for (const auto& ring : geom) {
         GeometryCoordinates coordinates;
         coordinates.reserve(ring.size());
         for (const auto& point : ring) {
             coordinates.emplace_back(point);
         }
         collection.push_back(std::move(coordinates));
     }
     return collection;
 }
 GeometryCollection operator()(const mapbox::geometry::multi_polygon<int16_t>& geom) const {
     GeometryCollection collection;
     for (auto& polygon : geom) {
         for (auto& ring : polygon) {
             GeometryCoordinates coordinates;
             coordinates.reserve(ring.size());
             for (auto& point : ring) {
                 coordinates.emplace_back(point);
             }
             collection.push_back(std::move(coordinates));
         }
     }
     return collection;
 }
bool pointIntersectsBufferedLine(const GeometryCoordinate& p, const GeometryCoordinates& line, const float radius) {
    const float radiusSquared = radius * radius;

    if (line.size() == 1) return util::distSqr<float>(p, line.at(0)) < radiusSquared;
    if (line.size() == 0) return false;

    for (auto i = line.begin() + 1; i != line.end(); i++) {
        // Find line segments that have a distance <= radius^2 to p
        // In that case, we treat the line as "containing point p".
        auto& v = *(i - 1);
        auto& w = *i;
        if (distToSegmentSquared(p, v, w) < radiusSquared) return true;
    }
    return false;
}
void CollisionFeature::bboxifyLabel(const GeometryCoordinates &line,
        GeometryCoordinate &anchorPoint, const int segment, const float labelLength, const float boxSize) {

    const float step = boxSize / 2;
    const unsigned int nBoxes = std::floor(labelLength / step);

    // offset the center of the first box by half a box so that the edge of the
    // box is at the edge of the label.
    const float firstBoxOffset = -boxSize / 2;

    GeometryCoordinate &p = anchorPoint;
    int index = segment + 1;
    float anchorDistance = firstBoxOffset;

    // move backwards along the line to the first segment the label appears on
    do {
        index--;

        // there isn't enough room for the label after the beginning of the line
        // checkMaxAngle should have already caught this
        if (index < 0) return;

        anchorDistance -= util::dist<float>(line[index], p);
        p = line[index];
    } while (anchorDistance > -labelLength / 2);

    float segmentLength = util::dist<float>(line[index], line[index + 1]);

    for (unsigned int i = 0; i < nBoxes; i++) {
        // the distance the box will be from the anchor
        const float boxDistanceToAnchor = -labelLength / 2 + i * step;

        // the box is not on the current segment. Move to the next segment.
        while (anchorDistance + segmentLength < boxDistanceToAnchor) {
            anchorDistance += segmentLength;
            index++;

            // There isn't enough room before the end of the line.
            if (index + 1 >= (int)line.size()) return;

            segmentLength = util::dist<float>(line[index], line[index + 1]);
        }

        // the distance the box will be from the beginning of the segment
        const float segmentBoxDistance = boxDistanceToAnchor - anchorDistance;

        const auto& p0 = line[index];
        const auto& p1 = line[index + 1];

        Point<float> boxAnchor = {
            p0.x + segmentBoxDistance / segmentLength * (p1.x - p0.x),
            p0.y + segmentBoxDistance / segmentLength * (p1.y - p0.y)
        };

        const float distanceToInnerEdge = std::max(std::fabs(boxDistanceToAnchor - firstBoxOffset) - step / 2, 0.0f);
        const float maxScale = labelLength / 2 / distanceToInnerEdge;

        boxes.emplace_back(boxAnchor, -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, maxScale);
    }
}
예제 #10
0
optional<VirtualSegment> getNextVirtualSegment(const VirtualSegment& previousVirtualSegment,
                                               const GeometryCoordinates& line,
                                               const float glyphDistanceFromAnchor,
                                               const bool glyphIsLogicallyForward) {
    auto nextSegmentBegin = previousVirtualSegment.end;
    
    auto end = nextSegmentBegin;
    size_t index = previousVirtualSegment.index;

    // skip duplicate nodes
    while (end == nextSegmentBegin) {
        // look ahead by 2 points in the line because the segment index refers to the beginning
        // of the segment, and we need an endpoint too
        if (glyphIsLogicallyForward && (index + 2 < line.size())) {
            index += 1;
        } else if (!glyphIsLogicallyForward && index != 0) {
            index -= 1;
        } else {
            return {};
        }
        
        end = getSegmentEnd(glyphIsLogicallyForward, line, index);
    }
    
    const auto anchor = getVirtualSegmentAnchor(nextSegmentBegin, end,
                                                util::dist<float>(previousVirtualSegment.anchor,
                                                                  previousVirtualSegment.end));
    return VirtualSegment {
        anchor,
        end,
        index,
        getMinScaleForSegment(glyphDistanceFromAnchor, anchor, end),
        previousVirtualSegment.minScale
    };
}
예제 #11
0
SymbolQuads getIconQuads(Anchor& anchor, const PositionedIcon& shapedIcon,
        const GeometryCoordinates& line, const SymbolLayoutProperties& layout,
        const bool alongLine) {

    auto image = *(shapedIcon.image);

    const float border = 1.0;
    auto left = shapedIcon.left - border;
    auto right = left + image.pos.w / image.relativePixelRatio;
    auto top = shapedIcon.top - border;
    auto bottom = top + image.pos.h / image.relativePixelRatio;
    vec2<float> tl{left, top};
    vec2<float> tr{right, top};
    vec2<float> br{right, bottom};
    vec2<float> bl{left, bottom};


    float angle = layout.icon.rotate * util::DEG2RAD;
    if (alongLine) {
        assert(static_cast<unsigned int>(anchor.segment) < line.size());
        const GeometryCoordinate &prev= line[anchor.segment];
        if (anchor.y == prev.y && anchor.x == prev.x &&
            static_cast<unsigned int>(anchor.segment + 1) < line.size()) {
            const GeometryCoordinate &next= line[anchor.segment + 1];
            angle += std::atan2(anchor.y - next.y, anchor.x - next.x) + M_PI;
        } else {
            angle += std::atan2(anchor.y - prev.y, anchor.x - prev.x);
        }
    }


    if (angle) {
        // Compute the transformation matrix.
        float angle_sin = std::sin(angle);
        float angle_cos = std::cos(angle);
        std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}};

        tl = tl.matMul(matrix);
        tr = tr.matMul(matrix);
        bl = bl.matMul(matrix);
        br = br.matMul(matrix);
    }

    SymbolQuads quads;
    quads.emplace_back(tl, tr, bl, br, image.pos, 0, anchor, globalMinScale, std::numeric_limits<float>::infinity());
    return quads;
}
예제 #12
0
static ClipperLib::Path toClipperPath(const GeometryCoordinates& ring) {
    ClipperLib::Path result;
    result.reserve(ring.size());
    for (const auto& p : ring) {
        result.emplace_back(p.x, p.y);
    }
    return result;
}
bool polygonIntersectsBox(const LineString<float>& polygon, const GridIndex<IndexedSubfeature>::BBox& bbox) {
    // This is just a wrapper that allows us to use the integer-based util::polygonIntersectsPolygon
    // Conversion limits our query accuracy to single-pixel resolution
    GeometryCoordinates integerPolygon;
    for (const auto& point : polygon) {
        integerPolygon.push_back(convertPoint<int16_t>(point));
    }
    int16_t minX1 = bbox.min.x;
    int16_t maxY1 = bbox.max.y;
    int16_t minY1 = bbox.min.y;
    int16_t maxX1 = bbox.max.x;

    auto bboxPoints = GeometryCoordinates {
        { minX1, minY1 }, { maxX1, minY1 }, { maxX1, maxY1 }, { minX1, maxY1 }
    };
    
    return util::polygonIntersectsPolygon(integerPolygon, bboxPoints);
}
예제 #14
0
GeometryCollection VectorTileFeature::getGeometries() const {
    uint8_t cmd = 1;
    uint32_t length = 0;
    int32_t x = 0;
    int32_t y = 0;
    const float scale = float(util::EXTENT) / layer.extent;

    GeometryCollection lines;

    lines.emplace_back();
    GeometryCoordinates* line = &lines.back();

    auto g_itr = geometry_iter.begin();
    while (g_itr != geometry_iter.end()) {
        if (length == 0) {
            uint32_t cmd_length = static_cast<uint32_t>(*g_itr++);
            cmd = cmd_length & 0x7;
            length = cmd_length >> 3;
        }

        --length;

        if (cmd == 1 || cmd == 2) {
            x += protozero::decode_zigzag32(static_cast<uint32_t>(*g_itr++));
            y += protozero::decode_zigzag32(static_cast<uint32_t>(*g_itr++));

            if (cmd == 1 && !line->empty()) { // moveTo
                lines.emplace_back();
                line = &lines.back();
            }

            line->emplace_back(::round(x * scale), ::round(y * scale));

        } else if (cmd == 7) { // closePolygon
            if (!line->empty()) {
                line->push_back((*line)[0]);
            }

        } else {
            throw std::runtime_error("unknown command");
        }
    }
예제 #15
0
static double signedArea(const GeometryCoordinates& ring) {
    double sum = 0;

    for (std::size_t i = 0, len = ring.size(), j = len - 1; i < len; j = i++) {
        const GeometryCoordinate& p1 = ring[i];
        const GeometryCoordinate& p2 = ring[j];
        sum += (p2.x - p1.x) * (p1.y + p2.y);
    }

    return sum;
}
예제 #16
0
GeometryCollection VectorTileFeature::getGeometries() const {
    pbf data(geometry_pbf);
    uint8_t cmd = 1;
    uint32_t length = 0;
    int32_t x = 0;
    int32_t y = 0;

    GeometryCollection lines;

    lines.emplace_back();
    GeometryCoordinates* line = &lines.back();

    while (data.data < data.end) {
        if (length == 0) {
            uint32_t cmd_length = data.varint();
            cmd = cmd_length & 0x7;
            length = cmd_length >> 3;
        }

        --length;

        if (cmd == 1 || cmd == 2) {
            x += data.svarint();
            y += data.svarint();

            if (cmd == 1 && !line->empty()) { // moveTo
                lines.emplace_back();
                line = &lines.back();
            }

            line->emplace_back(x, y);

        } else if (cmd == 7) { // closePolygon
            if (!line->empty()) {
                line->push_back((*line)[0]);
            }

        } else {
            throw std::runtime_error("unknown command");
        }
    }
예제 #17
0
optional<GeometryCoordinates> FeatureIndex::translateQueryGeometry(
        const GeometryCoordinates& queryGeometry,
        const std::array<float, 2>& translate,
        const style::TranslateAnchorType anchorType,
        const float bearing,
        const float pixelsToTileUnits) {
    if (translate[0] == 0 && translate[1] == 0) {
        return {};
    }

    GeometryCoordinate translateVec(translate[0] * pixelsToTileUnits, translate[1] * pixelsToTileUnits);
    if (anchorType == style::TranslateAnchorType::Viewport) {
        translateVec = util::rotate(translateVec, -bearing);
    }

    GeometryCoordinates translated;
    for (const auto& p : queryGeometry) {
        translated.push_back(p - translateVec);
    }
    return translated;
}
bool polygonIntersectsBufferedMultiLine(const GeometryCoordinates& polygon, const GeometryCollection& multiLine, float radius) {
    for (auto& line : multiLine) {
        if (polygon.size() >= 3) {
            for (auto& p : line) {
                if (polygonContainsPoint(polygon, p)) return true;
            }
        }

        if (lineIntersectsBufferedLine(polygon, line, radius)) return true;
    }

    return false;
}
예제 #19
0
/*
 Given (1) a glyph positioned relative to an anchor point and (2) a line to follow,
 calculates which segment of the line the glyph will fall on for each possible
 scale range, and for each range produces a "virtual" anchor point and an angle that will
 place the glyph on the right segment and rotated to the correct angle.
 
 Because one glyph quad is made ahead of time for each possible orientation, the
 symbol_sdf shader can quickly handle changing layout as we zoom in and out
 
 If the "keepUpright" property is set, we call getLineGlyphs twice (once upright and 
 once "upside down"). This will generate two sets of glyphs following the line in opposite
 directions. Later, SymbolLayout::place will look at the glyphs and based on the placement
 angle determine if their original anchor was "upright" or not -- based on that, it throws
 away one set of glyphs or the other (this work has to be done in the CPU, but it's just a
 filter so it's fast)
 */
void getLineGlyphs(std::back_insert_iterator<GlyphInstances> glyphs,
                      Anchor& anchor,
                      float glyphHorizontalOffsetFromAnchor,
                      const GeometryCoordinates& line,
                      size_t anchorSegment,
                      bool upsideDown) {
    assert(line.size() > anchorSegment+1);
    
    // This is true if the glyph is "logically forward" of the anchor point, based on the ordering of line segments
    //  The actual angle of the line is irrelevant
    //  If "upsideDown" is set, everything is flipped
    const bool glyphIsLogicallyForward = (glyphHorizontalOffsetFromAnchor >= 0) ^ upsideDown;
    const float glyphDistanceFromAnchor = std::fabs(glyphHorizontalOffsetFromAnchor);
    
    const auto initialSegmentEnd = getSegmentEnd(glyphIsLogicallyForward, line, anchorSegment);
    VirtualSegment virtualSegment = {
        anchor.point,
        initialSegmentEnd,
        anchorSegment,
        getMinScaleForSegment(glyphDistanceFromAnchor, anchor.point, initialSegmentEnd),
        std::numeric_limits<float>::infinity()
    };
    
    while (true) {
        insertSegmentGlyph(glyphs,
                           virtualSegment,
                           glyphIsLogicallyForward,
                           upsideDown);
        
        if (virtualSegment.minScale <= anchor.scale) {
            // No need to calculate below the scale where the label starts showing
            return;
        }
        
        optional<VirtualSegment> nextVirtualSegment = getNextVirtualSegment(virtualSegment,
                                                                            line,
                                                                            glyphDistanceFromAnchor,
                                                                            glyphIsLogicallyForward);
        if (!nextVirtualSegment) {
            // There are no more segments, so we can't fit this glyph on the line at a lower scale
            // This implies we can't show the label at all at lower scale, so we update the anchor's min scale
            anchor.scale = virtualSegment.minScale;
            return;
        } else {
            virtualSegment = *nextVirtualSegment;
        }
    }
    
}
bool lineIntersectsBufferedLine(const GeometryCoordinates& lineA, const GeometryCoordinates& lineB, float radius) {
    if (lineA.size() > 1) {
        if (lineIntersectsLine(lineA, lineB)) return true;

        // Check whether any point in either line is within radius of the other line
        for (auto& p : lineB) {
            if (pointIntersectsBufferedLine(p, lineA, radius)) return true;
        }
    }

    for (auto& p : lineA) {
        if (pointIntersectsBufferedLine(p, lineB, radius)) return true;
    }

    return false;
}
bool lineIntersectsLine(const GeometryCoordinates& lineA, const GeometryCoordinates& lineB) {
    if (lineA.size() == 0 || lineB.size() == 0) return false;
    for (auto i = lineA.begin(); i != lineA.end() - 1; i++) {
        auto& a0 = *i;
        auto& a1 = *(i + 1);
        for (auto j = lineB.begin(); j != lineB.end() - 1; j++) {
            auto& b0 = *j;
            auto& b1 = *(j + 1);
            if (lineSegmentIntersectsLineSegment(a0, a1, b0, b1)) return true;
        }
    }
    return false;
}
예제 #22
0
static GeometryCoordinates fromClipperPath(const ClipperLib::Path& path) {
    GeometryCoordinates result;
    result.reserve(path.size() + 1);
    
    result.reserve(path.size());
    for (const auto& p : path) {
        using Coordinate = GeometryCoordinates::coordinate_type;
        assert(p.x >= std::numeric_limits<Coordinate>::min());
        assert(p.x <= std::numeric_limits<Coordinate>::max());
        assert(p.y >= std::numeric_limits<Coordinate>::min());
        assert(p.y <= std::numeric_limits<Coordinate>::max());
        result.emplace_back(Coordinate(p.x), Coordinate(p.y));
    }
    
    // Clipper does not repeat initial point, but our geometry model requires it.
    if (!result.empty()) {
        result.push_back(result.front());
    }
    
    return result;
}
예제 #23
0
Anchors getAnchors(const GeometryCoordinates &line, float spacing,
        const float maxAngle, const float textLeft, const float textRight,
        const float iconLeft, const float iconRight,
        const float glyphSize, const float boxScale, const float overscaling) {
    if (line.empty()) return {};

    // Resample a line to get anchor points for labels and check that each
    // potential label passes text-max-angle check and has enough froom to fit
    // on the line.

    const float angleWindowSize = (textLeft - textRight) != 0.0f ?
        3.0f / 5.0f * glyphSize * boxScale :
        0;

    const float labelLength = fmax(textRight - textLeft, iconRight - iconLeft);

    // Is the line continued from outside the tile boundary?
    const bool continuedLine = (line[0].x == 0 || line[0].x == util::EXTENT || line[0].y == 0 || line[0].y == util::EXTENT);

    // Is the label long, relative to the spacing?
    // If so, adjust the spacing so there is always a minimum space of `spacing / 4` between label edges.
    if (spacing - labelLength * boxScale  < spacing / 4) {
        spacing = labelLength * boxScale + spacing / 4;
    }

    // Offset the first anchor by:
    // Either half the label length plus a fixed extra offset if the line is not continued
    // Or half the spacing if the line is continued.

    // For non-continued lines, add a bit of fixed extra offset to avoid collisions at T intersections.
    const float fixedExtraOffset = glyphSize * 2;

    const float offset = !continuedLine ?
    std::fmod((labelLength / 2 + fixedExtraOffset) * boxScale * overscaling, spacing) :
    std::fmod(spacing / 2 * overscaling, spacing);

    return resample(line, offset, spacing, angleWindowSize, maxAngle, labelLength * boxScale, continuedLine, false);
}
예제 #24
0
void LineBucket::addGeometry(const GeometryCoordinates& vertices) {
    const GLsizei len = [&vertices] {
        GLsizei l = static_cast<GLsizei>(vertices.size());
        // If the line has duplicate vertices at the end, adjust length to remove them.
        while (l > 2 && vertices[l - 1] == vertices[l - 2]) {
            l--;
        }
        return l;
    }();

    if (len < 2) {
        // fprintf(stderr, "a line must have at least two vertices\n");
        return;
    }

    const float miterLimit = layout.join == JoinType::Bevel ? 1.05f : float(layout.miterLimit);

    const double sharpCornerOffset = SHARP_CORNER_OFFSET * (util::EXTENT / (util::tileSize * overscaling));

    const GeometryCoordinate firstVertex = vertices.front();
    const GeometryCoordinate lastVertex = vertices[len - 1];
    const bool closed = firstVertex == lastVertex;

    if (len == 2 && closed) {
        // fprintf(stderr, "a line may not have coincident points\n");
        return;
    }

    const CapType beginCap = layout.cap;
    const CapType endCap = closed ? CapType::Butt : CapType(layout.cap);

    int8_t flip = 1;
    double distance = 0;
    bool startOfLine = true;
    GeometryCoordinate currentVertex = GeometryCoordinate::null(), prevVertex = GeometryCoordinate::null(),
               nextVertex = GeometryCoordinate::null();
    vec2<double> prevNormal = vec2<double>::null(), nextNormal = vec2<double>::null();

    // the last three vertices added
    e1 = e2 = e3 = -1;

    if (closed) {
        currentVertex = vertices[len - 2];
        nextNormal = util::perp(util::unit(vec2<double>(firstVertex - currentVertex)));
    }

    const GLint startVertex = vertexBuffer.index();
    std::vector<TriangleElement> triangleStore;

    for (GLsizei i = 0; i < len; ++i) {
        if (closed && i == len - 1) {
            // if the line is closed, we treat the last vertex like the first
            nextVertex = vertices[1];
        } else if (i + 1 < len) {
            // just the next vertex
            nextVertex = vertices[i + 1];
        } else {
            // there is no next vertex
            nextVertex = GeometryCoordinate::null();
        }

        // if two consecutive vertices exist, skip the current one
        if (nextVertex && vertices[i] == nextVertex) {
            continue;
        }

        if (nextNormal) {
            prevNormal = nextNormal;
        }
        if (currentVertex) {
            prevVertex = currentVertex;
        }

        currentVertex = vertices[i];

        // Calculate the normal towards the next vertex in this line. In case
        // there is no next vertex, pretend that the line is continuing straight,
        // meaning that we are just using the previous normal.
        nextNormal = nextVertex ? util::perp(util::unit(vec2<double>(nextVertex - currentVertex)))
                                : prevNormal;

        // If we still don't have a previous normal, this is the beginning of a
        // non-closed line, so we're doing a straight "join".
        if (!prevNormal) {
            prevNormal = nextNormal;
        }

        // Determine the normal of the join extrusion. It is the angle bisector
        // of the segments between the previous line and the next line.
        vec2<double> joinNormal = util::unit(prevNormal + nextNormal);

        /*  joinNormal     prevNormal
         *             ↖      ↑
         *                .________. prevVertex
         *                |
         * nextNormal  ←  |  currentVertex
         *                |
         *     nextVertex !
         *
         */

        // Calculate the length of the miter (the ratio of the miter to the width).
        // Find the cosine of the angle between the next and join normals
        // using dot product. The inverse of that is the miter length.
        const float cosHalfAngle = joinNormal.x * nextNormal.x + joinNormal.y * nextNormal.y;
        const float miterLength = cosHalfAngle != 0 ? 1 / cosHalfAngle: 1;

        const bool isSharpCorner = cosHalfAngle < COS_HALF_SHARP_CORNER && prevVertex && nextVertex;

        if (isSharpCorner && i > 0) {
            const double prevSegmentLength = util::dist<double>(currentVertex, prevVertex);
            if (prevSegmentLength > 2.0 * sharpCornerOffset) {
                GeometryCoordinate newPrevVertex = currentVertex - (util::round(vec2<double>(currentVertex - prevVertex) * (sharpCornerOffset / prevSegmentLength)));
                distance += util::dist<double>(newPrevVertex, prevVertex);
                addCurrentVertex(newPrevVertex, flip, distance, prevNormal, 0, 0, false, startVertex, triangleStore);
                prevVertex = newPrevVertex;
            }
        }

        // The join if a middle vertex, otherwise the cap
        const bool middleVertex = prevVertex && nextVertex;
        JoinType currentJoin = layout.join;
        const CapType currentCap = nextVertex ? beginCap : endCap;

        if (middleVertex) {
            if (currentJoin == JoinType::Round) {
                if (miterLength < layout.roundLimit) {
                    currentJoin = JoinType::Miter;
                } else if (miterLength <= 2) {
                    currentJoin = JoinType::FakeRound;
                }
            }

            if (currentJoin == JoinType::Miter && miterLength > miterLimit) {
                currentJoin = JoinType::Bevel;
            }

            if (currentJoin == JoinType::Bevel) {
                // The maximum extrude length is 128 / 63 = 2 times the width of the line
                // so if miterLength >= 2 we need to draw a different type of bevel where.
                if (miterLength > 2) {
                    currentJoin = JoinType::FlipBevel;
                }

                // If the miterLength is really small and the line bevel wouldn't be visible,
                // just draw a miter join to save a triangle.
                if (miterLength < miterLimit) {
                    currentJoin = JoinType::Miter;
                }
            }
        }

        // Calculate how far along the line the currentVertex is
        if (prevVertex)
            distance += util::dist<double>(currentVertex, prevVertex);

        if (middleVertex && currentJoin == JoinType::Miter) {
            joinNormal = joinNormal * miterLength;
            addCurrentVertex(currentVertex, flip, distance, joinNormal, 0, 0, false, startVertex,
                             triangleStore);

        } else if (middleVertex && currentJoin == JoinType::FlipBevel) {
            // miter is too big, flip the direction to make a beveled join

            if (miterLength > 100) {
                // Almost parallel lines
                joinNormal = nextNormal;
            } else {
                const float direction = prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x > 0 ? -1 : 1;
                const float bevelLength = miterLength * util::mag(prevNormal + nextNormal) /
                                          util::mag(prevNormal - nextNormal);
                joinNormal = util::perp(joinNormal) * bevelLength * direction;
            }

            addCurrentVertex(currentVertex, flip, distance, joinNormal, 0, 0, false, startVertex,
                             triangleStore);

            addCurrentVertex(currentVertex, -flip, distance, joinNormal, 0, 0, false, startVertex,
                             triangleStore);
        } else if (middleVertex && (currentJoin == JoinType::Bevel || currentJoin == JoinType::FakeRound)) {
            const bool lineTurnsLeft = flip * (prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x) > 0;
            const float offset = -std::sqrt(miterLength * miterLength - 1);
            float offsetA;
            float offsetB;

            if (lineTurnsLeft) {
                offsetB = 0;
                offsetA = offset;
            } else {
                offsetA = 0;
                offsetB = offset;
            }

            // Close previous segement with bevel
            if (!startOfLine) {
                addCurrentVertex(currentVertex, flip, distance, prevNormal, offsetA, offsetB, false,
                                 startVertex, triangleStore);
            }

            if (currentJoin == JoinType::FakeRound) {
                // The join angle is sharp enough that a round join would be visible.
                // Bevel joins fill the gap between segments with a single pie slice triangle.
                // Create a round join by adding multiple pie slices. The join isn't actually round, but
                // it looks like it is at the sizes we render lines at.

                // Add more triangles for sharper angles.
                // This math is just a good enough approximation. It isn't "correct".
                const int n = std::floor((0.5 - (cosHalfAngle - 0.5)) * 8);

                for (int m = 0; m < n; m++) {
                    auto approxFractionalJoinNormal = util::unit(nextNormal * ((m + 1.0f) / (n + 1.0f)) + prevNormal);
                    addPieSliceVertex(currentVertex, flip, distance, approxFractionalJoinNormal, lineTurnsLeft, startVertex, triangleStore);
                }

                addPieSliceVertex(currentVertex, flip, distance, joinNormal, lineTurnsLeft, startVertex, triangleStore);

                for (int k = n - 1; k >= 0; k--) {
                    auto approxFractionalJoinNormal = util::unit(prevNormal * ((k + 1.0f) / (n + 1.0f)) + nextNormal);
                    addPieSliceVertex(currentVertex, flip, distance, approxFractionalJoinNormal, lineTurnsLeft, startVertex, triangleStore);
                }
            }

            // Start next segment
            if (nextVertex) {
                addCurrentVertex(currentVertex, flip, distance, nextNormal, -offsetA, -offsetB,
                                 false, startVertex, triangleStore);
            }

        } else if (!middleVertex && currentCap == CapType::Butt) {
            if (!startOfLine) {
                // Close previous segment with a butt
                addCurrentVertex(currentVertex, flip, distance, prevNormal, 0, 0, false,
                                 startVertex, triangleStore);
            }

            // Start next segment with a butt
            if (nextVertex) {
                addCurrentVertex(currentVertex, flip, distance, nextNormal, 0, 0, false,
                                 startVertex, triangleStore);
            }

        } else if (!middleVertex && currentCap == CapType::Square) {
            if (!startOfLine) {
                // Close previous segment with a square cap
                addCurrentVertex(currentVertex, flip, distance, prevNormal, 1, 1, false,
                                 startVertex, triangleStore);

                // The segment is done. Unset vertices to disconnect segments.
                e1 = e2 = -1;
                flip = 1;
            }

            // Start next segment
            if (nextVertex) {
                addCurrentVertex(currentVertex, flip, distance, nextNormal, -1, -1, false,
                                 startVertex, triangleStore);
            }

        } else if (middleVertex ? currentJoin == JoinType::Round : currentCap == CapType::Round) {
            if (!startOfLine) {
                // Close previous segment with a butt
                addCurrentVertex(currentVertex, flip, distance, prevNormal, 0, 0, false,
                                 startVertex, triangleStore);

                // Add round cap or linejoin at end of segment
                addCurrentVertex(currentVertex, flip, distance, prevNormal, 1, 1, true, startVertex,
                                 triangleStore);

                // The segment is done. Unset vertices to disconnect segments.
                e1 = e2 = -1;
                flip = 1;
            }

            // Start next segment with a butt
            if (nextVertex) {
                // Add round cap before first segment
                addCurrentVertex(currentVertex, flip, distance, nextNormal, -1, -1, true,
                                 startVertex, triangleStore);

                addCurrentVertex(currentVertex, flip, distance, nextNormal, 0, 0, false,
                                 startVertex, triangleStore);
            }
        }

        if (isSharpCorner && i < len - 1) {
            const double nextSegmentLength = util::dist<double>(currentVertex, nextVertex);
            if (nextSegmentLength > 2 * sharpCornerOffset) {
                GeometryCoordinate newCurrentVertex = currentVertex + util::round(vec2<double>(nextVertex - currentVertex) * (sharpCornerOffset / nextSegmentLength));
                distance += util::dist<double>(newCurrentVertex, currentVertex);
                addCurrentVertex(newCurrentVertex, flip, distance, nextNormal, 0, 0, false, startVertex, triangleStore);
                currentVertex = newCurrentVertex;
            }
        }

        startOfLine = false;
    }

    const GLsizei endVertex = vertexBuffer.index();
    const GLsizei vertexCount = endVertex - startVertex;

    // Store the triangle/line groups.
    {
        if (triangleGroups.empty() || (triangleGroups.back()->vertex_length + vertexCount > 65535)) {
            // Move to a new group because the old one can't hold the geometry.
            triangleGroups.emplace_back(std::make_unique<TriangleGroup>());
        }

        assert(triangleGroups.back());
        auto& group = *triangleGroups.back();
        for (const auto& triangle : triangleStore) {
            triangleElementsBuffer.add(group.vertex_length + triangle.a,
                                       group.vertex_length + triangle.b,
                                       group.vertex_length + triangle.c);
        }

        group.vertex_length += vertexCount;
        group.elements_length += triangleStore.size();
    }
}
void CollisionFeature::bboxifyLabel(const GeometryCoordinates& line, GeometryCoordinate& anchorPoint,
                                    const int segment, const float labelLength, const float boxSize, const float overscaling) {
    const float step = boxSize / 2;
    const int nBoxes = std::floor(labelLength / step);
    // We calculate line collision circles out to 300% of what would normally be our
    // max size, to allow collision detection to work on labels that expand as
    // they move into the distance
    // Vertically oriented labels in the distant field can extend past this padding
    // This is a noticeable problem in overscaled tiles where the pitch 0-based
    // symbol spacing will put labels very close together in a pitched map.
    // To reduce the cost of adding extra collision circles, we slowly increase
    // them for overscaled tiles.
    const float overscalingPaddingFactor = 1 + .4 * ::log2(static_cast<double>(overscaling));
    const int nPitchPaddingBoxes = std::floor(nBoxes * overscalingPaddingFactor / 2);

    // offset the center of the first box by half a box so that the edge of the
    // box is at the edge of the label.
    const float firstBoxOffset = -boxSize / 2;

    GeometryCoordinate &p = anchorPoint;
    int index = segment + 1;
    float anchorDistance = firstBoxOffset;
    const float labelStartDistance = -labelLength / 2;
    const float paddingStartDistance = labelStartDistance - labelLength / 8;

    // move backwards along the line to the first segment the label appears on
    do {
        index--;

        if (index < 0) {
            if (anchorDistance > labelStartDistance) {
                // there isn't enough room for the label after the beginning of the line
                // checkMaxAngle should have already caught this
                return;
            } else {
                // The line doesn't extend far enough back for all of our padding,
                // but we got far enough to show the label under most conditions.
                index = 0;
                break;
            }
        }

        anchorDistance -= util::dist<float>(line[index], p);
        p = line[index];
    } while (anchorDistance > paddingStartDistance);

    auto segmentLength = util::dist<float>(line[index], line[index + 1]);

    for (int i = -nPitchPaddingBoxes; i < nBoxes + nPitchPaddingBoxes; i++) {
        // the distance the box will be from the anchor
        const float boxOffset = i * step;
        float boxDistanceToAnchor = labelStartDistance + boxOffset;

         // make the distance between pitch padding boxes bigger
         if (boxOffset < 0) boxDistanceToAnchor += boxOffset;
         if (boxOffset > labelLength) boxDistanceToAnchor += boxOffset - labelLength;

        if (boxDistanceToAnchor < anchorDistance) {
            // The line doesn't extend far enough back for this box, skip it
            // (This could allow for line collisions on distant tiles)
            continue;
        }

        // the box is not on the current segment. Move to the next segment.
        while (anchorDistance + segmentLength < boxDistanceToAnchor) {
            anchorDistance += segmentLength;
            index++;

            // There isn't enough room before the end of the line.
            if (index + 1 >= (int)line.size()) return;

            segmentLength = util::dist<float>(line[index], line[index + 1]);
        }

        // the distance the box will be from the beginning of the segment
        const float segmentBoxDistance = boxDistanceToAnchor - anchorDistance;

        const auto& p0 = line[index];
        const auto& p1 = line[index + 1];

        Point<float> boxAnchor = {
            p0.x + segmentBoxDistance / segmentLength * (p1.x - p0.x),
            p0.y + segmentBoxDistance / segmentLength * (p1.y - p0.y)
        };
        
        // If the box is within boxSize of the anchor, force the box to be used
        // (so even 0-width labels use at least one box)
        // Otherwise, the .8 multiplication gives us a little bit of conservative
        // padding in choosing which boxes to use (see CollisionIndex#placedCollisionCircles)
        const float paddedAnchorDistance = std::abs(boxDistanceToAnchor - firstBoxOffset) < step ?
            0 :
            (boxDistanceToAnchor - firstBoxOffset) * 0.8;

        boxes.emplace_back(boxAnchor, boxAnchor - convertPoint<float>(anchorPoint), -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, paddedAnchorDistance, boxSize / 2);
    }
}
void RenderImageSource::update(Immutable<style::Source::Impl> baseImpl_,
                               const std::vector<Immutable<Layer::Impl>>&,
                               const bool needsRendering,
                               const bool,
                               const TileParameters& parameters) {
    enabled = needsRendering;
    if (!needsRendering) {
        return;
    }

    auto transformState = parameters.transformState;
    std::swap(baseImpl, baseImpl_);

    auto coords = impl().getCoordinates();
    std::shared_ptr<PremultipliedImage> image = impl().getImage();

    if (!image || !image->valid()) {
        enabled = false;
        return;
    }

    // Compute the z0 tile coordinates for the given LatLngs
    TileCoordinatePoint nePoint = { -INFINITY, -INFINITY };
    TileCoordinatePoint swPoint = { INFINITY, INFINITY };
    std::vector<TileCoordinatePoint> tileCoordinates;
    for (LatLng latLng : coords) {
        auto point = TileCoordinate::fromLatLng(0, latLng).p;
        tileCoordinates.push_back(point);
        swPoint.x = std::min(swPoint.x, point.x);
        nePoint.x = std::max(nePoint.x, point.x);
        swPoint.y = std::min(swPoint.y, point.y);
        nePoint.y = std::max(nePoint.y, point.y);
   }

    // Calculate the optimum zoom level to determine the tile ids to use for transforms
    auto dx = nePoint.x - swPoint.x;
    auto dy = nePoint.y - swPoint.y;
    auto dMax = std::max(dx, dy);
    double zoom = std::max(0.0, std::floor(-util::log2(dMax)));

    // Only enable if the long side of the image is > 2 pixels. Resulting in a
    // display of at least 2 x 1 px image
    // A tile coordinate unit represents the length of one tile (tileSize) at a given zoom.
    // To convert a tile coordinate to pixels, multiply by tileSize.
    // Here dMax is in z0 tile units, so we also scale by 2^z to match current zoom.
    enabled = dMax * std::pow(2.0, transformState.getZoom()) * util::tileSize > 2.0;
    if (!enabled) {
        return;
    }

    auto imageBounds = LatLngBounds::hull(coords[0], coords[1]);
    imageBounds.extend(coords[2]);
    imageBounds.extend(coords[3]);
    auto tileCover = util::tileCover(imageBounds, zoom);
    tileIds.clear();
    tileIds.push_back(tileCover[0]);

    bool hasVisibleTile = false;
    // Add additional wrapped tile ids if neccessary
    auto idealTiles = util::tileCover(transformState, transformState.getZoom());
    for (auto tile : idealTiles) {
        if (tile.wrap != 0 && tileCover[0].canonical.isChildOf(tile.canonical)) {
            tileIds.push_back({ tile.wrap, tileCover[0].canonical });
            hasVisibleTile = true;
        }
        else if (!hasVisibleTile) {
            for (auto coveringTile: tileCover) {
                if(coveringTile.canonical == tile.canonical ||
                    coveringTile.canonical.isChildOf(tile.canonical) ||
                    tile.canonical.isChildOf(coveringTile.canonical)) {
                    hasVisibleTile = true;
                }
            }
        }
    }

    enabled = hasVisibleTile;
    if (!enabled) {
        return;
    }

    // Calculate Geometry Coordinates based on tile cover at ideal zoom
    GeometryCoordinates geomCoords;
    for (auto tileCoords : tileCoordinates) {
        auto gc = TileCoordinate::toGeometryCoordinate(tileIds[0], tileCoords);
        geomCoords.push_back(gc);
    }
    if (!bucket) {
        bucket = std::make_unique<RasterBucket>(image);
    } else {
        bucket->clear();
        if (image != bucket->image) {
            bucket->setImage(image);
        }
    }

    // Set Bucket Vertices, Indices, and segments
    bucket->vertices.emplace_back(
        RasterProgram::layoutVertex({ geomCoords[0].x, geomCoords[0].y }, { 0, 0 }));
    bucket->vertices.emplace_back(
        RasterProgram::layoutVertex({ geomCoords[1].x, geomCoords[1].y }, { util::EXTENT, 0 }));
    bucket->vertices.emplace_back(
        RasterProgram::layoutVertex({ geomCoords[3].x, geomCoords[3].y }, { 0, util::EXTENT }));
    bucket->vertices.emplace_back(
        RasterProgram::layoutVertex({ geomCoords[2].x, geomCoords[2].y }, { util::EXTENT, util::EXTENT }));

    bucket->indices.emplace_back(0, 1, 2);
    bucket->indices.emplace_back(1, 2, 3);

    bucket->segments.emplace_back(0, 0, 4, 6);
}
void ShapeAnnotationImpl::updateTile(const TileID& tileID, AnnotationTile& tile) {
    static const double baseTolerance = 4;

    if (!shapeTiler) {
        const uint64_t maxAmountOfTiles = 1 << maxZoom;
        const double tolerance = baseTolerance / (maxAmountOfTiles * GeometryTileFeature::defaultExtent);

        geojsonvt::ProjectedRings rings;
        std::vector<geojsonvt::LonLat> points;

        for (size_t i = 0; i < shape.segments[0].size(); ++i) { // first segment for now (no holes)
            const double constrainedLatitude = util::clamp(shape.segments[0][i].latitude, -util::LATITUDE_MAX, util::LATITUDE_MAX);
            points.push_back(geojsonvt::LonLat(shape.segments[0][i].longitude, constrainedLatitude));
        }

        if (type == geojsonvt::ProjectedFeatureType::Polygon &&
                (points.front().lon != points.back().lon || points.front().lat != points.back().lat)) {
            points.push_back(geojsonvt::LonLat(points.front().lon, points.front().lat));
        }

        auto ring = geojsonvt::Convert::projectRing(points, tolerance);
        rings.push_back(ring);

        std::vector<geojsonvt::ProjectedFeature> features;
        features.push_back(geojsonvt::Convert::create(geojsonvt::Tags(), type, rings));

        mapbox::geojsonvt::Options options;
        options.maxZoom = maxZoom;
        options.buffer = 255u;
        options.extent = util::EXTENT;
        options.tolerance = baseTolerance;
        shapeTiler = std::make_unique<mapbox::geojsonvt::GeoJSONVT>(features, options);
    }

    const auto& shapeTile = shapeTiler->getTile(tileID.sourceZ, tileID.x, tileID.y);
    if (!shapeTile)
        return;

    AnnotationTileLayer& layer = *tile.layers.emplace(layerID,
                                 std::make_unique<AnnotationTileLayer>()).first->second;

    for (auto& shapeFeature : shapeTile.features) {
        FeatureType featureType = FeatureType::Unknown;

        if (shapeFeature.type == geojsonvt::TileFeatureType::LineString) {
            featureType = FeatureType::LineString;
        } else if (shapeFeature.type == geojsonvt::TileFeatureType::Polygon) {
            featureType = FeatureType::Polygon;
        }

        assert(featureType != FeatureType::Unknown);

        GeometryCollection renderGeometry;
        for (auto& shapeRing : shapeFeature.tileGeometry.get<geojsonvt::TileRings>()) {
            GeometryCoordinates renderLine;

            for (auto& shapePoint : shapeRing) {
                renderLine.emplace_back(shapePoint.x, shapePoint.y);
            }

            renderGeometry.push_back(renderLine);
        }

        layer.features.emplace_back(
            std::make_shared<AnnotationTileFeature>(featureType, renderGeometry));
    }
}
예제 #28
0
bool checkMaxAngle(const GeometryCoordinates &line, Anchor &anchor, const float labelLength,
        const float windowSize, const float maxAngle) {

    // horizontal labels always pass
    if (anchor.segment < 0) return true;

    GeometryCoordinate anchorPoint = convertPoint<int16_t>(anchor.point);
    GeometryCoordinate &p = anchorPoint;
    int index = anchor.segment + 1;
    float anchorDistance = 0;

    // move backwards along the line to the first segment the label appears on
    while (anchorDistance > -labelLength / 2) {
        index--;

        // there isn't enough room for the label after the beginning of the line
        if (index < 0) return false;

        anchorDistance -= util::dist<float>(line[index], p);
        p = line[index];
    }

    anchorDistance += util::dist<float>(line[index], line[index + 1]);
    index++;

    // store recent corners and their total angle difference
    std::queue<Corner> recentCorners;
    float recentAngleDelta = 0;

     // move forwards by the length of the label and check angles along the way
    while (anchorDistance < labelLength / 2) {

        // there isn't enough room for the label before the end of the line
        if (index + 1 >= (int)line.size()) return false;

        auto& prev = line[index - 1];
        auto& current = line[index];
        auto& next = line[index + 1];

        float angleDelta = util::angle_to(prev, current) - util::angle_to(current, next);
        // restrict angle to -pi..pi range
        angleDelta = std::fabs(std::fmod(angleDelta + 3 * M_PI, M_PI * 2) - M_PI);

        recentCorners.emplace(anchorDistance, angleDelta);
        recentAngleDelta += angleDelta;

        // remove corners that are far enough away from the list of recent anchors
        while (anchorDistance - recentCorners.front().distance > windowSize) {
            recentAngleDelta -= recentCorners.front().angleDelta;
            recentCorners.pop();
        }

        // the sum of angles within the window area exceeds the maximum allowed value. check fails.
        if (recentAngleDelta > maxAngle) return false;

        index++;
        anchorDistance += util::dist<float>(current, next);
    }

    // no part of the line had an angle greater than the maximum allowed. check passes.
    return true;


}
예제 #29
0
	optional<PlacedGlyph> placeGlyphAlongLine(const float offsetX, const float lineOffsetX, const float lineOffsetY, const bool flip,
            const Point<float>& projectedAnchorPoint, const Point<float>& tileAnchorPoint, const uint16_t anchorSegment, const GeometryCoordinates& line, const std::vector<float>& tileDistances, const mat4& labelPlaneMatrix, const bool returnTileDistance) {

        const float combinedOffsetX = flip ?
            offsetX - lineOffsetX :
            offsetX + lineOffsetX;

        int16_t dir = combinedOffsetX > 0 ? 1 : -1;

        float angle = 0.0;
        if (flip) {
            // The label needs to be flipped to keep text upright.
            // Iterate in the reverse direction.
            dir *= -1;
            angle = M_PI;
        }

        if (dir < 0) angle += M_PI;

        int32_t currentIndex = dir > 0 ? anchorSegment : anchorSegment + 1;

        const int32_t initialIndex = currentIndex;
        Point<float> current = projectedAnchorPoint;
        Point<float> prev = projectedAnchorPoint;
        float distanceToPrev = 0.0;
        float currentSegmentDistance = 0.0;
        const float absOffsetX = std::abs(combinedOffsetX);

        while (distanceToPrev + currentSegmentDistance <= absOffsetX) {
            currentIndex += dir;

            // offset does not fit on the projected line
            if (currentIndex < 0 || currentIndex >= static_cast<int32_t>(line.size())) {
                return {};
            }

            prev = current;
            PointAndCameraDistance projection = project(convertPoint<float>(line.at(currentIndex)), labelPlaneMatrix);
            if (projection.second > 0) {
                current = projection.first;
            } else {
                // The vertex is behind the plane of the camera, so we can't project it
                // Instead, we'll create a vertex along the line that's far enough to include the glyph
                const Point<float> previousTilePoint = distanceToPrev == 0 ?
                    tileAnchorPoint :
                    convertPoint<float>(line.at(currentIndex - dir));
                const Point<float> currentTilePoint = convertPoint<float>(line.at(currentIndex));
                current = projectTruncatedLineSegment(previousTilePoint, currentTilePoint, prev, absOffsetX - distanceToPrev + 1, labelPlaneMatrix);
            }

            distanceToPrev += currentSegmentDistance;
            currentSegmentDistance = util::dist<float>(prev, current);
        }

        // The point is on the current segment. Interpolate to find it.
        const float segmentInterpolationT = (absOffsetX - distanceToPrev) / currentSegmentDistance;
        const Point<float> prevToCurrent = current - prev;
        Point<float> p = (prevToCurrent * segmentInterpolationT) + prev;

        // offset the point from the line to text-offset and icon-offset
        p += util::perp(prevToCurrent) * static_cast<float>(lineOffsetY * dir / util::mag(prevToCurrent));

        const float segmentAngle = angle + std::atan2(current.y - prev.y, current.x - prev.x);

        return {{
            p,
            segmentAngle,
            returnTileDistance ?
                TileDistance(
                    (currentIndex - dir) == initialIndex ? 0 : tileDistances[currentIndex - dir],
                    absOffsetX - distanceToPrev
                ) :
                optional<TileDistance>()
        }};
    }
예제 #30
0
SymbolQuad getIconQuad(const Anchor& anchor,
                       const PositionedIcon& shapedIcon,
                       const GeometryCoordinates& line,
                       const SymbolLayoutProperties::Evaluated& layout,
                       const float layoutTextSize,
                       const style::SymbolPlacementType placement, 
                       const Shaping& shapedText) {
    const ImagePosition& image = shapedIcon.image();

    // If you have a 10px icon that isn't perfectly aligned to the pixel grid it will cover 11 actual
    // pixels. The quad needs to be padded to account for this, otherwise they'll look slightly clipped
    // on one edge in some cases.
    const float border = 1.0;

    float top = shapedIcon.top() - border / image.pixelRatio;
    float left = shapedIcon.left() - border / image.pixelRatio;
    float bottom = shapedIcon.bottom() + border / image.pixelRatio;
    float right = shapedIcon.right() + border / image.pixelRatio;
    Point<float> tl;
    Point<float> tr;
    Point<float> br;
    Point<float> bl;

    if (layout.get<IconTextFit>() != IconTextFitType::None && shapedText) {
        auto iconWidth = right - left;
        auto iconHeight = bottom - top;
        auto size = layoutTextSize / 24.0f;
        auto textLeft = shapedText.left * size;
        auto textRight = shapedText.right * size;
        auto textTop = shapedText.top * size;
        auto textBottom = shapedText.bottom * size;
        auto textWidth = textRight - textLeft;
        auto textHeight = textBottom - textTop;
        auto padT = layout.get<IconTextFitPadding>()[0];
        auto padR = layout.get<IconTextFitPadding>()[1];
        auto padB = layout.get<IconTextFitPadding>()[2];
        auto padL = layout.get<IconTextFitPadding>()[3];
        auto offsetY = layout.get<IconTextFit>() == IconTextFitType::Width ? (textHeight - iconHeight) * 0.5 : 0;
        auto offsetX = layout.get<IconTextFit>() == IconTextFitType::Height ? (textWidth - iconWidth) * 0.5 : 0;
        auto width = layout.get<IconTextFit>() == IconTextFitType::Width || layout.get<IconTextFit>() == IconTextFitType::Both ? textWidth : iconWidth;
        auto height = layout.get<IconTextFit>() == IconTextFitType::Height || layout.get<IconTextFit>() == IconTextFitType::Both ? textHeight : iconHeight;
        left = textLeft + offsetX - padL;
        top = textTop + offsetY - padT;
        right = textLeft + offsetX + padR + width;
        bottom = textTop + offsetY + padB + height;
        tl = {left, top};
        tr = {right, top};
        br = {right, bottom};
        bl = {left, bottom};
    } else {
        tl = {left, top};
        tr = {right, top};
        br = {right, bottom};
        bl = {left, bottom};
    }

    float angle = shapedIcon.angle();
    if (placement == style::SymbolPlacementType::Line) {
        assert(static_cast<unsigned int>(anchor.segment) < line.size());
        const GeometryCoordinate &prev= line[anchor.segment];
        if (anchor.point.y == prev.y && anchor.point.x == prev.x &&
            static_cast<unsigned int>(anchor.segment + 1) < line.size()) {
            const GeometryCoordinate &next= line[anchor.segment + 1];
            angle += std::atan2(anchor.point.y - next.y, anchor.point.x - next.x) + M_PI;
        } else {
            angle += std::atan2(anchor.point.y - prev.y, anchor.point.x - prev.x);
        }
    }

    if (angle) {
        // Compute the transformation matrix.
        float angle_sin = std::sin(angle);
        float angle_cos = std::cos(angle);
        std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}};

        tl = util::matrixMultiply(matrix, tl);
        tr = util::matrixMultiply(matrix, tr);
        bl = util::matrixMultiply(matrix, bl);
        br = util::matrixMultiply(matrix, br);
    }

    // Icon quad is padded, so texture coordinates also need to be padded.
    Rect<uint16_t> textureRect {
        static_cast<uint16_t>(image.textureRect.x - border),
        static_cast<uint16_t>(image.textureRect.y - border),
        static_cast<uint16_t>(image.textureRect.w + border * 2),
        static_cast<uint16_t>(image.textureRect.h + border * 2)
    };

    return SymbolQuad { tl, tr, bl, br, textureRect, 0, 0, anchor.point, globalMinScale, std::numeric_limits<float>::infinity(), shapedText.writingMode };
}