All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.appengine.repackaged.com.google.common.geometry.S2CellUnion Maven / Gradle / Ivy

Go to download

SDK for dev_appserver (local development) with some of the dependencies shaded (repackaged)

There is a newer version: 2.0.31
Show newest version
/*
 * Copyright 2005 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.appengine.repackaged.com.google.common.geometry;

import static com.google.appengine.repackaged.com.google.common.geometry.S2Projections.PROJ;

import com.google.appengine.repackaged.com.google.common.annotations.GwtCompatible;
import com.google.appengine.repackaged.com.google.common.collect.Lists;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * An S2CellUnion is a region consisting of cells of various sizes. Typically a cell union is used
 * to approximate some other shape. There is a tradeoff between the accuracy of the approximation
 * and how many cells are used. Unlike polygons, cells have a fixed hierarchical structure. This
 * makes them more suitable for optimizations based on preprocessing.
 *
 * 

An S2CellUnion is represented as a vector of sorted, non-overlapping S2CellIds. By default the * vector is also "normalized", meaning that groups of 4 child cells have been replaced by their * parent cell whenever possible. S2CellUnions are not required to be normalized, but certain * operations will return different results if they are not, e.g. {@link #contains(S2CellUnion)}. * */ @GwtCompatible(serializable = true) public strictfp class S2CellUnion implements S2Region, Iterable, Serializable { private static final long serialVersionUID = 1L; private static final byte LOSSLESS_ENCODING_VERSION = 1; /** The CellIds that form the Union */ private ArrayList cellIds = new ArrayList(); public S2CellUnion() {} /** * Populates a cell union with the given S2CellIds, and then calls normalize(). This directly uses * the input list, without copying it. */ public void initFromCellIds(ArrayList cellIds) { initRawCellIds(cellIds); normalize(); } /** Populates a cell union with the given 64-bit cell ids, and then calls normalize(). */ public void initFromIds(List cellIds) { initRawIds(cellIds); normalize(); } /** * Populates a cell union with the given S2CellIds. The input list is copied, and then cleared. */ public void initSwap(List cellIds) { initRawSwap(cellIds); normalize(); } /** * Populates a cell union with the given S2CellIds. This does not call normalize, see {@link * #initRawSwap} for details. This directly uses the input list, without copying it. */ public void initRawCellIds(ArrayList cellIds) { this.cellIds = cellIds; } /** * Populates a cell union with the given 64 bit cell ids. This does not call normalize, see {@link * #initRawSwap} for details. The input list is copied. */ // TODO(eengle): Make a constructed S2CellUnion immutable, and port other init methods from C++. public void initRawIds(List cellIds) { int size = cellIds.size(); this.cellIds = new ArrayList(size); for (Long id : cellIds) { this.cellIds.add(new S2CellId(id)); } } /** * Like the initFrom*() constructors, but does not call normalize(). The cell union *must* be * normalized before doing any calculations with it, so it is the caller's * responsibility to * make sure that the input is normalized. This method is useful when converting cell unions to * another representation and back. * *

The input list is copied, and then cleared. */ public void initRawSwap(List cellIds) { this.cellIds = new ArrayList(cellIds); cellIds.clear(); } /** * Create a cell union that corresponds to a continuous range of cell ids. The output is a * normalized collection of cell ids that covers the leaf cells between "minId" and "maxId" * inclusive. * *

Requires that minId.isLeaf(), maxId.isLeaf(), and minId <= maxId. */ public void initFromMinMax(S2CellId minId, S2CellId maxId) { // assert minId.isLeaf(); // assert maxId.isLeaf(); // assert minId.compareTo(maxId) <= 0; // assert minId.isValid() && maxId.isValid(); initFromBeginEnd(minId, maxId.next()); } /** * As {@link #initFromMinMax(S2CellId, S2CellId)}, except that the union covers the range of leaf * cells from "begin" (inclusive) to "end" (exclusive.) If {@code begin.equals(end)}, the result * is empty. * *

Requires that begin.isLeaf(), end.isLeaf(), and begin <= end. */ public void initFromBeginEnd(S2CellId begin, S2CellId end) { // assert (begin.isLeaf()); // assert (end.isLeaf()); // assert (begin.compareTo(end) <= 0); // We repeatedly add the largest cell we can, in sorted order. cellIds.clear(); for (S2CellId nextBegin = begin; nextBegin.compareTo(end) < 0; ) { // assert(nextBegin.isLeaf()); // Find the largest cell that starts at "next_begin" and ends before "end". S2CellId nextId = nextBegin; while (!nextId.isFace() && nextId.parent().rangeMin().equals(nextBegin) && nextId.parent().rangeMax().compareTo(end) < 0) { nextId = nextId.parent(); } cellIds.add(nextId); nextBegin = nextId.rangeMax().next(); } // The output should already be sorted and normalized. // assert(!normalize()); } public int size() { return cellIds.size(); } /** Convenience methods for accessing the individual cell ids. */ public S2CellId cellId(int i) { return cellIds.get(i); } /** Enable iteration over the union's cells. */ @Override public Iterator iterator() { return cellIds.iterator(); } /** Direct access to the underlying vector for iteration . */ public ArrayList cellIds() { return cellIds; } /** * Returns true if the cell union is valid, meaning that the S2CellIds are non-overlapping and * sorted in increasing order. */ public boolean isValid() { for (int i = 1; i < cellIds.size(); i++) { if (cellIds.get(i - 1).rangeMax().compareTo(cellIds.get(i).rangeMin()) >= 0) { return false; } } return true; } /** * Returns true if the cell union is normalized, meaning that it {@link #isValid()} is true and * that no four cells have a common parent. * *

Certain operations such as {@link #contains(S2CellUnion)} may return a different result if * the cell union is not normalized. */ public boolean isNormalized() { for (int i = 1; i < cellIds.size(); i++) { if (cellIds.get(i - 1).rangeMax().compareTo(cellIds.get(i).rangeMin()) >= 0) { return false; } if (i >= 3 && areSiblings( cellIds.get(i - 3), cellIds.get(i - 2), cellIds.get(i - 1), cellIds.get(i))) { return false; } } return true; } /** * Returns true if the given four cells have a common parent. * *

Requires the four cells are distinct. */ private static boolean areSiblings(S2CellId a, S2CellId b, S2CellId c, S2CellId d) { // A necessary (but not sufficient) condition is that the XOR of the four cells must be zero. // This is also very fast to test. if ((a.id() ^ b.id() ^ c.id()) != d.id()) { return false; } // Now we do a slightly more expensive but exact test. First, compute a mask that blocks out // the two bits that encode the child position of "id" with respect to its parent, then check // that the other three children all agree with "mask". long mask = d.lowestOnBit() << 1; mask = ~(mask + (mask << 1)); long idMasked = d.id() & mask; return !d.isFace() && (a.id() & mask) == idMasked && (b.id() & mask) == idMasked && (c.id() & mask) == idMasked; } /** * Replaces "output" with an expanded version of the cell union where any cells whose level is * less than "min_level" or where (level - min_level) is not a multiple of "level_mod" are * replaced by their children, until either both of these conditions are satisfied or the maximum * level is reached. * *

This method allows a covering generated by S2RegionCoverer using min_level() or level_mod() * constraints to be stored as a normalized cell union (which allows various geometric * computations to be done) and then converted back to the original list of cell ids that * satisfies the desired constraints. */ public void denormalize(int minLevel, int levelMod, ArrayList output) { // assert (minLevel >= 0 && minLevel <= S2CellId.MAX_LEVEL); // assert (levelMod >= 1 && levelMod <= 3); output.clear(); output.ensureCapacity(size()); for (S2CellId id : this) { int level = id.level(); int newLevel = Math.max(minLevel, level); if (levelMod > 1) { // Round up so that (new_level - min_level) is a multiple of level_mod. // (Note that S2CellId::kMaxLevel is a multiple of 1, 2, and 3.) newLevel += (S2CellId.MAX_LEVEL - (newLevel - minLevel)) % levelMod; newLevel = Math.min(S2CellId.MAX_LEVEL, newLevel); } if (newLevel == level) { output.add(id); } else { S2CellId end = id.childEnd(newLevel); for (id = id.childBegin(newLevel); !id.equals(end); id = id.next()) { output.add(id); } } } } /** * If there are more than "excess" elements of the cell_ids() vector that are allocated but * unused, reallocate the array to eliminate the excess space. This reduces memory usage when many * cell unions need to be held in memory at once. */ public void pack() { cellIds.trimToSize(); } /** * Return true if the cell union contains the given cell id. Containment is defined with respect * to regions, e.g. a cell contains its 4 children. This is a fast operation (logarithmic in the * size of the cell union). * *

CAVEAT: If you have constructed a valid but non-normalized S2CellUnion, note that groups of * 4 child cells are not considered to contain their parent cell. To get this behavior * you must construct a normalized cell union, or call {@link #normalize()} prior to this method. */ public boolean contains(S2CellId id) { // This is an exact test. Each cell occupies a linear span of the S2 // space-filling curve, and the cell id is simply the position at the center // of this span. The cell union ids are sorted in increasing order along // the space-filling curve. So we simply find the pair of cell ids that // surround the given cell id (using binary search). There is containment // if and only if one of these two cell ids contains this cell. int pos = Collections.binarySearch(cellIds, id); if (pos < 0) { pos = -pos - 1; } if (pos < cellIds.size() && cellIds.get(pos).rangeMin().lessOrEquals(id)) { return true; } return pos != 0 && cellIds.get(pos - 1).rangeMax().greaterOrEquals(id); } /** * Return true if the cell union intersects the given cell id. This is a fast operation * (logarithmic in the size of the cell union). */ public boolean intersects(S2CellId id) { // This is an exact test; see the comments for Contains() above. int pos = Collections.binarySearch(cellIds, id); if (pos < 0) { pos = -pos - 1; } if (pos < cellIds.size() && cellIds.get(pos).rangeMin().lessOrEquals(id.rangeMax())) { return true; } return pos != 0 && cellIds.get(pos - 1).rangeMax().greaterOrEquals(id.rangeMin()); } /** * Returns true if this cell union contains {@code that}. * *

CAVEAT: If you have constructed a valid but non-normalized S2CellUnion, note that groups of * 4 child cells are not considered to contain their parent cell. To get this behavior * you must construct a normalized cell union, or call {@link #normalize()} prior to this method. */ public boolean contains(S2CellUnion that) { S2CellUnion result = new S2CellUnion(); result.getIntersection(this, that); return result.cellIds.equals(that.cellIds); } /** This is a fast operation (logarithmic in the size of the cell union). */ @Override public boolean contains(S2Cell cell) { return contains(cell.id()); } /** Return true if this cell union intersects {@code union}. */ public boolean intersects(S2CellUnion union) { S2CellUnion result = new S2CellUnion(); result.getIntersection(this, union); return result.size() > 0; } /** Sets this cell union to the union of {@code x} and {@code y}. */ public void getUnion(S2CellUnion x, S2CellUnion y) { // assert (x != this && y != this); cellIds.clear(); cellIds.ensureCapacity(x.size() + y.size()); cellIds.addAll(x.cellIds); cellIds.addAll(y.cellIds); normalize(); } /** * Specialized version of GetIntersection() that gets the intersection of a cell union with the * given cell id. This can be useful for "splitting" a cell union into chunks. * *

Note: {@code x} and {@code y} must be normalized. */ public void getIntersection(S2CellUnion x, S2CellId id) { // assert (x != this); cellIds.clear(); if (x.contains(id)) { cellIds.add(id); } else { int pos = Collections.binarySearch(x.cellIds, id.rangeMin()); if (pos < 0) { pos = -pos - 1; } S2CellId idmax = id.rangeMax(); int size = x.cellIds.size(); while (pos < size && x.cellIds.get(pos).lessOrEquals(idmax)) { cellIds.add(x.cellIds.get(pos++)); } } // assert isNormalized() || !x.isNormalized(); } /** * Initializes this cell union to the intersection of the two given cell unions. Requires: x != * this and y != this. * *

Note: {@code x} and {@code y} must be normalized. */ public void getIntersection(S2CellUnion x, S2CellUnion y) { getIntersection(x.cellIds, y.cellIds, cellIds); // The output is normalized as long as at least one input is normalized. // assert isNormalized() || (!x.isNormalized() && !y.isNormalized()); } /** * Like {@code #getIntersection(S2CellUnion, S2CellUnion)}, but works directly with lists of * S2CellIds, and this method has slightly more relaxed normalization requirements: the input * vectors may contain groups of 4 child cells that all have the same parent. (In a normalized * S2CellUnion, such groups are always replaced by the parent cell.) * *

Note: {@code x} and {@code y} must be sorted. */ public static void getIntersection(List x, List y, List results) { // assert (x != results && y != results); // This is a fairly efficient calculation that uses binary search to skip // over sections of both input vectors. It takes constant time if all the // cells of "x" come before or after all the cells of "y" in S2CellId order. results.clear(); int i = 0; int j = 0; while (i < x.size() && j < y.size()) { S2CellId xCell = x.get(i); S2CellId xMin = xCell.rangeMin(); S2CellId yCell = y.get(j); S2CellId yMin = yCell.rangeMin(); if (xMin.greaterThan(yMin)) { // Either j->contains(xCell) or the two cells are disjoint. if (xCell.lessOrEquals(yCell.rangeMax())) { results.add(xCell); i++; } else { // Advance "j" to the first cell possibly contained by xCell. j = indexedBinarySearch(y, xMin, j + 1); // The previous cell *(j-1) may now contain xCell. if (xCell.lessOrEquals(y.get(j - 1).rangeMax())) { --j; } } } else if (yMin.greaterThan(xMin)) { // Identical to the code above with "i" and "j" reversed. if (yCell.lessOrEquals(xCell.rangeMax())) { results.add(yCell); j++; } else { i = indexedBinarySearch(x, yMin, i + 1); if (yCell.lessOrEquals(x.get(i - 1).rangeMax())) { --i; } } } else { // "i" and "j" have the same rangeMin(), so one contains the other. if (xCell.lessThan(yCell)) { results.add(xCell); i++; } else { results.add(yCell); j++; } } } } /** Initiaizes this cell union to the difference of the two given cell unions. */ public void getDifference(S2CellUnion x, S2CellUnion y) { // TODO(user): this is approximately O(N*log(N)), but could probably use similar techniques as // getIntersection() to be more efficient. cellIds.clear(); for (S2CellId id : x) { getDifferenceInternal(id, y); } // The output is normalized as long as the first argument is normalized. // assert isNormalized() || !x.isNormalized(); } private void getDifferenceInternal(S2CellId cell, S2CellUnion y) { // Add the difference between cell and y to cellIds. If they intersect but the difference is // non-empty, divide and conquer. if (!y.intersects(cell)) { cellIds.add(cell); } else if (!y.contains(cell)) { for (int i = 0; i < 4; i++) { getDifferenceInternal(cell.child(i), y); } } } /** * Just as normal binary search, except that it allows specifying the starting value for the lower * bound. * * @return The position of the searched element in the list (if found), or the position where the * element could be inserted without violating the order. */ private static int indexedBinarySearch(List l, S2CellId key, int low) { int high = l.size() - 1; while (low <= high) { int mid = (low + high) >> 1; S2CellId midVal = l.get(mid); int cmp = midVal.compareTo(key); if (cmp < 0) { low = mid + 1; } else if (cmp > 0) { high = mid - 1; } else { return mid; // key found } } return low; // key not found } /** * Expands the cell union such that it contains all cells of the given level that are adjacent to * any cell of the original union. Two cells are defined as adjacent if their boundaries have any * points in common, i.e. most cells have 8 adjacent cells (not counting the cell itself). * *

Note that the size of the output is exponential in "level". For example, if level == 20 and * the input has a cell at level 10, there will be on the order of 4000 adjacent cells in the * output. For most applications the Expand(min_fraction, min_distance) method below is easier to * use. */ public void expand(int level) { ArrayList output = new ArrayList(); long levelLsb = S2CellId.lowestOnBitForLevel(level); for (int i = size(); --i >= 0; ) { S2CellId id = cellId(i); if (id.lowestOnBit() < levelLsb) { id = id.parent(level); // Optimization: skip over any cells contained by this one. This is // especially important when very small regions are being expanded. while (i > 0 && id.contains(cellId(i - 1))) { --i; } } output.add(id); id.getAllNeighbors(level, output); } initSwap(output); } /** * Expand the cell union such that it contains all points whose distance to the cell union is at * most minRadius, but do not use cells that are more than maxLevelDiff levels higher than the * largest cell in the input. The second parameter controls the tradeoff between accuracy and * output size when a large region is being expanded by a small amount (e.g. expanding Canada by * 1km). * *

For example, if maxLevelDiff == 4, the region will always be expanded by approximately 1/16 * the width of its largest cell. Note that in the worst case, the number of cells in the output * can be up to 4 * (1 + 2 ** maxLevelDiff) times larger than the number of cells in the input. */ public void expand(S1Angle minRadius, int maxLevelDiff) { int minLevel = S2CellId.MAX_LEVEL; for (S2CellId id : this) { minLevel = Math.min(minLevel, id.level()); } // Find the maximum level such that all cells are at least "min_radius" // wide. int radiusLevel = PROJ.minWidth.getMaxLevel(minRadius.radians()); if (radiusLevel == 0 && minRadius.radians() > PROJ.minWidth.getValue(0)) { // The requested expansion is greater than the width of a face cell. // The easiest way to handle this is to expand twice. expand(0); } expand(Math.min(minLevel + maxLevelDiff, radiusLevel)); } // NOTE: This should be marked as @Override, but clone() isn't present in GWT's version of // Object, so we can't mark it as such. @SuppressWarnings("MissingOverride") public S2Region clone() { S2CellUnion copy = new S2CellUnion(); copy.initRawCellIds(Lists.newArrayList(cellIds)); return copy; } @Override public S2Cap getCapBound() { // Compute the approximate centroid of the region. This won't produce the // bounding cap of minimal area, but it should be close enough. if (cellIds.isEmpty()) { return S2Cap.empty(); } S2Point centroid = S2Point.ORIGIN; for (S2CellId id : this) { double area = S2Cell.averageArea(id.level()); centroid = S2Point.add(centroid, S2Point.mul(id.toPoint(), area)); } if (centroid.equalsPoint(S2Point.ORIGIN)) { centroid = S2Point.X_POS; } else { centroid = S2Point.normalize(centroid); } // Use the centroid as the cap axis, and expand the cap angle so that it // contains the bounding caps of all the individual cells. Note that it is // *not* sufficient to just bound all the cell vertices because the bounding // cap may be concave (i.e. cover more than one hemisphere). S2Cap cap = S2Cap.fromAxisChord(centroid, S1ChordAngle.ZERO); for (S2CellId id : this) { cap = cap.addCap(new S2Cell(id).getCapBound()); } return cap; } @Override public S2LatLngRect getRectBound() { S2LatLngRect.Builder builder = S2LatLngRect.Builder.empty(); for (S2CellId id : this) { builder.union(new S2Cell(id).getRectBound()); } return builder.build(); } /** This is a fast operation (logarithmic in the size of the cell union). */ @Override public boolean mayIntersect(S2Cell cell) { return intersects(cell.id()); } /** * The point 'p' does not need to be normalized. This is a fast operation (logarithmic in the size * of the cell union). */ @Override public boolean contains(S2Point p) { return contains(S2CellId.fromPoint(p)); } /** * The number of leaf cells covered by the union. This will be no more than 6*2^60 for the whole * sphere. * * @return the number of leaf cells covered by the union */ public long leafCellsCovered() { long numLeaves = 0; for (S2CellId cellId : cellIds) { int invertedLevel = S2CellId.MAX_LEVEL - cellId.level(); numLeaves += (1L << (invertedLevel << 1)); } return numLeaves; } /** * Approximate this cell union's area by summing the average area of each contained cell's average * area, using {@link S2Cell#averageArea()}. This is equivalent to the number of leaves covered, * multiplied by the average area of a leaf. * *

Note that {@link S2Cell#averageArea()} does not take into account distortion of cell, and * thus may be off by up to a factor of 1.7. NOTE: Since this is proportional to * LeafCellsCovered(), it is always better to use the other function if all you care about is the * relative average area between objects. * * @return the sum of the average area of each contained cell's average area */ public double averageBasedArea() { return S2Cell.averageArea(S2CellId.MAX_LEVEL) * leafCellsCovered(); } /** * Calculates this cell union's area by summing the approximate area for each contained cell, * using {@link S2Cell#approxArea()}. * * @return approximate area of the cell union */ public double approxArea() { double area = 0; for (S2CellId cellId : cellIds) { area += new S2Cell(cellId).approxArea(); } return area; } /** * Calculates this cell union's area by summing the exact area for each contained cell, using the * {@link S2Cell#exactArea()}. * * @return the exact area of the cell union */ public double exactArea() { double area = 0; for (S2CellId cellId : cellIds) { area += new S2Cell(cellId).exactArea(); } return area; } /** Return true if two cell unions are identical. */ @Override public boolean equals(Object that) { if (!(that instanceof S2CellUnion)) { return false; } S2CellUnion union = (S2CellUnion) that; return this.cellIds.equals(union.cellIds); } @Override public int hashCode() { int value = 17; for (S2CellId id : this) { value = 37 * value + id.hashCode(); } return value; } /** * Normalizes the cell union by discarding cells that are contained by other cells, replacing * groups of 4 child cells by their parent cell whenever possible, and sorting all the cell ids in * increasing order. Returns true if the number of cells was reduced. * *

This method *must* be called before doing any calculations on the cell union, such as * Intersects() or Contains(). * * @return true if the normalize operation had any effect on the cell union, false if the union * was already normalized */ public boolean normalize() { return normalize(cellIds); } /** Like {@link #normalize()}, but works directly with a vector of S2CellIds. */ public static boolean normalize(List ids) { // Optimize the representation by looking for cases where all subcells of a parent cell are // present. Collections.sort(ids); int out = 0; for (int i = 0; i < ids.size(); i++) { S2CellId id = ids.get(i); // Check whether this cell is contained by the previous cell. if (out > 0 && ids.get(out - 1).contains(id)) { continue; } // Discard any previous cells contained by this cell. while (out > 0 && id.contains(ids.get(out - 1))) { out--; } // Check whether the last 3 elements of "output" plus "id" can be collapsed into a single // parent cell. while (out >= 3) { // A necessary (but not sufficient) condition is that the XOR of the // four cells must be zero. This is also very fast to test. if ((ids.get(out - 3).id() ^ ids.get(out - 2).id() ^ ids.get(out - 1).id()) != id.id()) { break; } // Now we do a slightly more expensive but exact test. First, compute a // mask that blocks out the two bits that encode the child position of // "id" with respect to its parent, then check that the other three // children all agree with "mask. long mask = id.lowestOnBit() << 1; mask = ~(mask + (mask << 1)); long idMasked = (id.id() & mask); if ((ids.get(out - 3).id() & mask) != idMasked || (ids.get(out - 2).id() & mask) != idMasked || (ids.get(out - 1).id() & mask) != idMasked || id.isFace()) { break; } // Replace four children by their parent cell. id = id.parent(); out -= 3; } ids.set(out++, id); } int size = ids.size(); boolean trimmed = out < size; while (out < size) { size--; ids.remove(size); } return trimmed; } /** * Writes a simple lossless encoding of this cell union to the given output stream. This encoding * uses 1 byte for a version number, and N+1 64-bit longs where the first is the number of longs * that follow. * * @throws IOException there is a problem writing to the underlying stream */ public void encode(OutputStream output) throws IOException { encode(new LittleEndianOutput(output)); } /** * As {@link #encode(OutputStream)}, but avoids creating a little endian output helper. * *

Use this method if a number of S2 objects will be encoded to the same underlying stream. */ public void encode(LittleEndianOutput output) throws IOException { output.writeByte(LOSSLESS_ENCODING_VERSION); output.writeLong(cellIds.size()); for (S2CellId cellId : this) { output.writeLong(cellId.id()); } } /** * Decodes an S2CellUnion encoded with Encode(). Returns true on success. * * @throws IOException there is a problem reading from the underlying stream, the version number * doesn't match, or the number of elements to read is not between 0 and 2^31-1. */ public static S2CellUnion decode(InputStream input) throws IOException { return decode(new LittleEndianInput(input)); } /** * As {@link #decode(InputStream)}, but avoids creating a little endian input helper. * *

Use this method if a number of S2 objects will be decoded from the same underlying stream. */ public static S2CellUnion decode(LittleEndianInput input) throws IOException { // Should contain at least version and vector length. byte version = input.readByte(); if (version != LOSSLESS_ENCODING_VERSION) { throw new IOException("Unrecognized version number " + version); } long numCells = input.readLong(); if (numCells < 0 || numCells > Integer.MAX_VALUE) { throw new IOException("Unsupported number of cells encountered: " + numCells); } S2CellUnion result = new S2CellUnion(); for (int i = 0; i < numCells; i++) { result.cellIds().add(new S2CellId(input.readLong())); } return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy