{"action":"created","comment":{"url":"https://api.github.com/repos/geoserver/geoserver/pulls/comments/171807065","pull_request_review_id":100722425,"id":171807065,"diff_hunk":"@@ -0,0 +1,395 @@\n+/* (c) 2018 Open Source Geospatial Foundation - all rights reserved\n+ * This code is licensed under the GPL 2.0 license, available at the root\n+ * application directory.\n+ */\n+package org.geoserver.wps.gs.download;\n+\n+import java.awt.geom.AffineTransform;\n+import java.io.IOException;\n+import java.util.ArrayList;\n+import java.util.HashMap;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.logging.Level;\n+import java.util.logging.Logger;\n+import java.util.stream.Collectors;\n+\n+import org.geotools.coverage.grid.GridGeometry2D;\n+import org.geotools.coverage.grid.io.DimensionDescriptor;\n+import org.geotools.coverage.grid.io.GranuleSource;\n+import org.geotools.coverage.grid.io.GridCoverage2DReader;\n+import org.geotools.coverage.grid.io.StructuredGridCoverage2DReader;\n+import org.geotools.data.Query;\n+import org.geotools.data.simple.SimpleFeatureCollection;\n+import org.geotools.data.simple.SimpleFeatureIterator;\n+import org.geotools.factory.GeoTools;\n+import org.geotools.geometry.GeneralEnvelope;\n+import org.geotools.geometry.jts.ReferencedEnvelope;\n+import org.geotools.referencing.CRS;\n+import org.geotools.referencing.operation.transform.ProjectiveTransform;\n+import org.geotools.resources.coverage.FeatureUtilities;\n+import org.geotools.util.logging.Logging;\n+import org.opengis.feature.simple.SimpleFeature;\n+import org.opengis.feature.simple.SimpleFeatureType;\n+import org.opengis.feature.type.GeometryDescriptor;\n+import org.opengis.filter.Filter;\n+import org.opengis.filter.expression.PropertyName;\n+import org.opengis.geometry.BoundingBox;\n+import org.opengis.referencing.FactoryException;\n+import org.opengis.referencing.NoSuchAuthorityCodeException;\n+import org.opengis.referencing.crs.CoordinateReferenceSystem;\n+import org.opengis.referencing.datum.PixelInCell;\n+import org.opengis.referencing.operation.MathTransform;\n+import org.opengis.referencing.operation.TransformException;\n+\n+/**\n+ * A class delegated to return the proper GridGeometry to be used by Raster Download\n+ * when target size is not specified. \n+ */\n+class GridGeometryProvider {\n+\n+    private static final Logger LOGGER = Logging.getLogger(GridGeometryProvider.class);\n+    \n+    /**\n+     * A trivial CRS cache in order to avoid several CRS.decode for the same EPSG \n+     */\n+    class CRSCache {\n+        Map<String, CoordinateReferenceSystem> cache = new HashMap<String, CoordinateReferenceSystem>();\n+        \n+        public CoordinateReferenceSystem getCrs(String crsId) throws NoSuchAuthorityCodeException, FactoryException {\n+            CoordinateReferenceSystem crs = null;\n+            if (!cache.containsKey(crsId)) {\n+                crs = CRS.decode(crsId);\n+                cache.put(crsId, crs);\n+            } else {\n+                crs = cache.get(crsId);    \n+            }\n+            return crs;\n+        }\n+    }\n+    \n+    /**\n+     * Class delegate to extract the resolution from a features collection\n+     * based on the available resolution related descriptors.  \n+     */\n+    class ResolutionProvider {\n+\n+        // Resolution descriptors\n+        private DimensionDescriptor resDescriptor;\n+\n+        private DimensionDescriptor resXDescriptor;\n+\n+        private DimensionDescriptor resYDescriptor;\n+\n+        // CRS descriptor\n+        private DimensionDescriptor crsDescriptor;\n+\n+        private boolean hasBothResolutions;\n+\n+        private boolean isHeterogeneousCrs;\n+        \n+        private CRSCache crsCache; \n+        \n+        public ResolutionProvider(Map<String, DimensionDescriptor> descriptors) {\n+            resDescriptor = descriptors.get(DimensionDescriptor.RESOLUTION);\n+            resXDescriptor = descriptors.get(DimensionDescriptor.RESOLUTION_X);\n+            resYDescriptor = descriptors.get(DimensionDescriptor.RESOLUTION_Y);\n+            crsDescriptor = descriptors.get(DimensionDescriptor.CRS);\n+            hasBothResolutions = resXDescriptor != null && resYDescriptor != null;\n+            isHeterogeneousCrs = crsDescriptor != null;\n+            crsCache = new CRSCache();\n+        }\n+\n+        /**\n+         * No resolution can be provided if there isn't any resolution related descriptor\n+         */\n+        boolean canCompute() {\n+            return resDescriptor != null || (resXDescriptor != null && resYDescriptor != null);\n+        }\n+\n+        /**\n+         * Get the best resolution from the input {@link SimpleFeatureCollection}.\n+         * \n+         * @param features\n+         * @param resolutions \n+         * @return\n+         * @throws NoSuchAuthorityCodeException\n+         * @throws FactoryException\n+         * @throws TransformException\n+         */\n+        public ReferencedEnvelope getBestResolution(SimpleFeatureCollection features, final double[] bestResolution)\n+                throws NoSuchAuthorityCodeException, FactoryException, TransformException {\n+            \n+            // Setting up features attributes to be checked\n+            final String resXAttribute = hasBothResolutions ? resXDescriptor.getStartAttribute()\n+                    : resDescriptor.getStartAttribute();\n+            final String resYAttribute = hasBothResolutions ? resYDescriptor.getStartAttribute()\n+                    : resDescriptor.getStartAttribute();\n+\n+            String crsAttribute = isHeterogeneousCrs ? crsDescriptor.getStartAttribute() : null;\n+            SimpleFeatureType schema = features.getSchema();\n+            CoordinateReferenceSystem schemaCrs = schema.getGeometryDescriptor().getCoordinateReferenceSystem();\n+\n+            // Iterate over the features to extract the best resolution\n+            BoundingBox featureBBox = null;\n+            GeneralEnvelope env = null;\n+            ReferencedEnvelope envelope = null;\n+            try (SimpleFeatureIterator iterator = features.features()) {\n+                double[] res = new double[2];\n+                while (iterator.hasNext()) {\n+                    SimpleFeature feature = iterator.next();\n+                    extractResolution(feature, resXAttribute, resYAttribute, crsAttribute,\n+                            schemaCrs, res);\n+\n+                    // Update bestResolution x and y\n+                    bestResolution[0] = res[0] < bestResolution[0] ? res[0] : bestResolution[0];\n+                    bestResolution[1] = res[1] < bestResolution[1] ? res[1] : bestResolution[1];\n+                    featureBBox = feature.getBounds();\n+\n+                    // Update the accessed envelope\n+                    if (env == null) {\n+                        env = new GeneralEnvelope(featureBBox);\n+                    } else {\n+                        env.add(featureBBox);\n+                    }\n+                }\n+            }\n+            if (env != null) {\n+                envelope = new ReferencedEnvelope(env);\n+            }\n+            return envelope;\n+        }\n+\n+        /**\n+         * Extract the resolution from the specified feature via the resolution attributes.\n+         * \n+         * @param feature\n+         * @param resXAttribute\n+         * @param resYAttribute\n+         * @param crsAttribute\n+         * @param schemaCrs\n+         * @param resolution\n+         * @throws NoSuchAuthorityCodeException\n+         * @throws FactoryException\n+         * @throws TransformException\n+         */\n+        private void extractResolution(SimpleFeature feature, String resXAttribute, String resYAttribute, \n+                String crsAttribute, CoordinateReferenceSystem schemaCrs, double[] resolution) \n+                        throws NoSuchAuthorityCodeException, FactoryException, TransformException {\n+            resolution[0] = (Double) feature.getAttribute(resXAttribute);\n+            resolution[1] = hasBothResolutions ? (Double) feature.getAttribute(resYAttribute) \n+                    : resolution[0];\n+            if (isHeterogeneousCrs) {\n+                String crsId = (String) feature.getAttribute(crsAttribute);\n+                CoordinateReferenceSystem granuleCrs = crsCache.getCrs(crsId);\n+                transformResolution(feature, schemaCrs, granuleCrs, resolution);\n+            }            \n+        }\n+\n+        /**\n+         * Compute the transformed resolution of the provided feature since we are in the case of\n+         * heterogeneous CRS.\n+         *\n+         * @param feature\n+         * @param schemaCrs\n+         * @param granuleCrs\n+         * @param resolution\n+         * @throws FactoryException\n+         * @throws TransformException\n+         */\n+        private void transformResolution(SimpleFeature feature, CoordinateReferenceSystem schemaCrs, \n+                CoordinateReferenceSystem granuleCrs, double[] resolution) throws FactoryException, TransformException {\n+            MathTransform transform = CRS.findMathTransform(schemaCrs, granuleCrs);\n+            \n+            // Do nothing if the CRS transformation is the identity\n+            if (!transform.isIdentity()) {\n+                BoundingBox bounds = feature.getBounds();\n+                MathTransform inverse = transform.inverse();\n+                \n+                // Get the center coordinate in the granule's CRS\n+                double center[] = new double[] {\n+                        (bounds.getMaxX() + bounds.getMinX()) / 2, \n+                        (bounds.getMaxY() + bounds.getMinY()) / 2};\n+                transform.transform(center, 0, center, 0, 1);    \n+                \n+                // Setup 2 segments in granule's CRS\n+                double [] coords = new double[6];\n+                double [] resCoords = new double[6];\n+\n+                // center\n+                coords[0] = center[0];\n+                coords[1] = center[1];\n+                \n+                // DX from center\n+                coords[2] = center[0] + resolution[0];\n+                coords[3] = center[1];\n+                \n+                // DY from center\n+                coords[4] = center[0];\n+                coords[5] = center[1] + resolution[1];\n+\n+                // Transform the coordinates back to schemaCrs\n+                inverse.transform(coords, 0, resCoords, 0, 3);\n+                \n+                double dx1 = resCoords[2] - resCoords[0];\n+                double dx2 = resCoords[3] - resCoords[1];\n+                double dy1 = resCoords[4] - resCoords[0];\n+                double dy2 = resCoords[5] - resCoords[1];\n+\n+                // Computing euclidean distances\n+                double transformedDX = Math.sqrt(dx1 * dx1 + dx2 * dx2);\n+                double transformedDY = Math.sqrt(dy1 * dy1 + dy2 * dy2);\n+                resolution[0] = transformedDX;\n+                resolution[1] = transformedDY;\n+            }\n+        }\n+    }\n+\n+    /** The underlying reader */\n+    private GridCoverage2DReader reader;\n+\n+    /** The ROIManager instance */\n+    private ROIManager roiManager;\n+\n+    /** The specified filter (may be null) */\n+    private Filter filter;\n+\n+    public GridGeometryProvider(GridCoverage2DReader reader, ROIManager roiManager, Filter filter) {\n+        this.reader = reader;\n+        this.roiManager = roiManager;\n+        this.filter = filter;\n+    }\n+\n+    /** \n+     * Compute the requested GridGeometry taking into account resolution dimensions descriptor (if any),\n+     * specified filter and ROI    \n+     */\n+    public GridGeometry2D getGridGeometry()\n+            throws TransformException, IOException, FactoryException {\n+        if (!StructuredGridCoverage2DReader.class.isAssignableFrom(reader.getClass())) {\n+\n+            //\n+            // CASE A: simple readers: return the native resolution gridGeometry\n+            //\n+            if (LOGGER.isLoggable(Level.FINE)) {\n+                LOGGER.fine(\"The underlying reader is not structured; returning native resolution\");\n+            }\n+            return getNativeResolutionGridGeometry();\n+        } else {\n+            //\n+            // CASE B: StructuredGridCoverage2DReader\n+            // \n+            StructuredGridCoverage2DReader structuredReader = (StructuredGridCoverage2DReader) reader;\n+            String coverageName = reader.getGridCoverageNames()[0];\n+\n+            Map<String, DimensionDescriptor> descriptors = structuredReader\n+                    .getDimensionDescriptors(coverageName).stream()\n+                    .collect(Collectors.toMap(dd -> dd.getName(), dd -> dd));\n+\n+            ResolutionProvider provider = new ResolutionProvider(descriptors);\n+\n+            //\n+            // Do we have any resolution descriptor available?\n+            // if not, go to standard computation.\n+            //\n+            if (!provider.canCompute()) {\n+                if (LOGGER.isLoggable(Level.FINE)) {\n+                    LOGGER.fine(\"The underlying reader is structured but no resolution descriptors are available\"\n+                            + \". Returning native resolution\");\n+                }\n+                return getNativeResolutionGridGeometry();\n+            }\n+\n+            GranuleSource granules = structuredReader.getGranules(coverageName, true);\n+\n+            // Setup a query on top of ROI and input filter (if any)\n+            Query query = initQuery(granules);\n+            SimpleFeatureCollection features = granules.getGranules(query);\n+\n+            if (features == null || features.isEmpty()) {\n+                if (LOGGER.isLoggable(Level.FINE)) {\n+                    LOGGER.fine(\"No features available for the specified query. Returning native resolution\");\n+                }\n+                return getNativeResolutionGridGeometry();\n+            }\n+            // Initialize resolution with infinite numbers\n+            double[] resolution = new double[] {Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY};\n+            ReferencedEnvelope envelope = provider.getBestResolution(features, resolution);\n+            AffineTransform at = new AffineTransform(resolution[0], 0, 0, -resolution[1], envelope.getMinX(), envelope.getMaxY());\n+            MathTransform tx = ProjectiveTransform.create(at);\n+            \n+            return new GridGeometry2D(PixelInCell.CELL_CORNER, tx, envelope, GeoTools.getDefaultHints());\n+        }\n+    }\n+\n+    /**\n+     * Initialize the query to get granules, based on provided filter and region of interest (if any)\n+     * \n+     * @param granules\n+     * @return\n+     * @throws TransformException\n+     * @throws FactoryException\n+     * @throws IOException\n+     */\n+    private Query initQuery(GranuleSource granules)\n+            throws TransformException, FactoryException, IOException {\n+        List<Filter> filters = new ArrayList<Filter>();\n+\n+        Query query = Query.ALL;\n+\n+        // Set bbox query if a ROI has been provided\n+        if (roiManager != null) {\n+            CoordinateReferenceSystem targetCRS = roiManager.getTargetCRS();\n+            GeometryDescriptor geomDescriptor = granules.getSchema().getGeometryDescriptor();\n+            CoordinateReferenceSystem indexCRS = geomDescriptor.getCoordinateReferenceSystem();\n+            ReferencedEnvelope envelope = null;\n+            if (targetCRS != null && !roiManager.isRoiCrsEqualsTargetCrs()) {\n+                envelope = new ReferencedEnvelope(\n+                    roiManager.getSafeRoiInTargetCRS().getEnvelopeInternal(), targetCRS);\n+                MathTransform reprojectionTrasform = null;\n+                if (!CRS.equalsIgnoreMetadata(targetCRS, indexCRS)) {\n+                    reprojectionTrasform = CRS.findMathTransform(targetCRS, indexCRS, true);\n+                    if (!reprojectionTrasform.isIdentity()) {\n+                        envelope = envelope.transform(indexCRS, true);\n+                    }\n+                }\n+            } else {\n+                envelope = new ReferencedEnvelope(roiManager.getSafeRoiInNativeCRS().getEnvelopeInternal(), indexCRS);\n+            }\n+\n+            final PropertyName geometryProperty = FeatureUtilities.DEFAULT_FILTER_FACTORY\n+                    .property(geomDescriptor.getName());\n+            filters.add(FeatureUtilities.DEFAULT_FILTER_FACTORY.bbox(geometryProperty, envelope));\n+        }\n+\n+        // Add the filter if specified\n+        if (filter != null) {\n+            filters.add(filter);\n+        }\n+\n+        // Set the query filter\n+        if (!filters.isEmpty()) {\n+            query = new Query();\n+            query.setFilter(FeatureUtilities.DEFAULT_FILTER_FACTORY.and(filters));","path":"src/community/wps-download/src/main/java/org/geoserver/wps/gs/download/GridGeometryProvider.java","position":373,"original_position":373,"commit_id":"55d063cf1a0eeda5cc6fcf6e1f5fab54ebabacfa","original_commit_id":"55d063cf1a0eeda5cc6fcf6e1f5fab54ebabacfa","user":{"login":"aaime","id":325433,"avatar_url":"https://avatars3.githubusercontent.com/u/325433?v=4","gravatar_id":"","url":"https://api.github.com/users/aaime","html_url":"https://github.com/aaime","followers_url":"https://api.github.com/users/aaime/followers","following_url":"https://api.github.com/users/aaime/following{/other_user}","gists_url":"https://api.github.com/users/aaime/gists{/gist_id}","starred_url":"https://api.github.com/users/aaime/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/aaime/subscriptions","organizations_url":"https://api.github.com/users/aaime/orgs","repos_url":"https://api.github.com/users/aaime/repos","events_url":"https://api.github.com/users/aaime/events{/privacy}","received_events_url":"https://api.github.com/users/aaime/received_events","type":"User","site_admin":false},"body":"An \"and of one\" is probably working but looks odd.  Since you have a list of filters, this method will do the right thing for you (you could dodge the if completely): https://github.com/geoserver/geoserver/blob/bab3171ef936628d28d6cbd0377a58f648716e9c/src/main/src/main/java/org/geoserver/catalog/Predicates.java#L240","created_at":"2018-03-02T10:05:34Z","updated_at":"2018-03-02T10:39:40Z","html_url":"https://github.com/geoserver/geoserver/pull/2772#discussion_r171807065","pull_request_url":"https://api.github.com/repos/geoserver/geoserver/pulls/2772","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/geoserver/geoserver/pulls/comments/171807065"},"html":{"href":"https://github.com/geoserver/geoserver/pull/2772#discussion_r171807065"},"pull_request":{"href":"https://api.github.com/repos/geoserver/geoserver/pulls/2772"}}},"pull_request":{"url":"https://api.github.com/repos/geoserver/geoserver/pulls/2772","id":171731562,"html_url":"https://github.com/geoserver/geoserver/pull/2772","diff_url":"https://github.com/geoserver/geoserver/pull/2772.diff","patch_url":"https://github.com/geoserver/geoserver/pull/2772.patch","issue_url":"https://api.github.com/repos/geoserver/geoserver/issues/2772","number":2772,"state":"open","locked":false,"title":"GEOS-8599: WPS download at granule's resolution","user":{"login":"dromagnoli","id":1278116,"avatar_url":"https://avatars3.githubusercontent.com/u/1278116?v=4","gravatar_id":"","url":"https://api.github.com/users/dromagnoli","html_url":"https://github.com/dromagnoli","followers_url":"https://api.github.com/users/dromagnoli/followers","following_url":"https://api.github.com/users/dromagnoli/following{/other_user}","gists_url":"https://api.github.com/users/dromagnoli/gists{/gist_id}","starred_url":"https://api.github.com/users/dromagnoli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dromagnoli/subscriptions","organizations_url":"https://api.github.com/users/dromagnoli/orgs","repos_url":"https://api.github.com/users/dromagnoli/repos","events_url":"https://api.github.com/users/dromagnoli/events{/privacy}","received_events_url":"https://api.github.com/users/dromagnoli/received_events","type":"User","site_admin":false},"body":"","created_at":"2018-02-27T17:27:32Z","updated_at":"2018-03-02T10:39:40Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b598bcc230ea9ce0f31e4b7f3bcde314a842907e","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/geoserver/geoserver/pulls/2772/commits","review_comments_url":"https://api.github.com/repos/geoserver/geoserver/pulls/2772/comments","review_comment_url":"https://api.github.com/repos/geoserver/geoserver/pulls/comments{/number}","comments_url":"https://api.github.com/repos/geoserver/geoserver/issues/2772/comments","statuses_url":"https://api.github.com/repos/geoserver/geoserver/statuses/55d063cf1a0eeda5cc6fcf6e1f5fab54ebabacfa","head":{"label":"dromagnoli:GEOS_8599","ref":"GEOS_8599","sha":"55d063cf1a0eeda5cc6fcf6e1f5fab54ebabacfa","user":{"login":"dromagnoli","id":1278116,"avatar_url":"https://avatars3.githubusercontent.com/u/1278116?v=4","gravatar_id":"","url":"https://api.github.com/users/dromagnoli","html_url":"https://github.com/dromagnoli","followers_url":"https://api.github.com/users/dromagnoli/followers","following_url":"https://api.github.com/users/dromagnoli/following{/other_user}","gists_url":"https://api.github.com/users/dromagnoli/gists{/gist_id}","starred_url":"https://api.github.com/users/dromagnoli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dromagnoli/subscriptions","organizations_url":"https://api.github.com/users/dromagnoli/orgs","repos_url":"https://api.github.com/users/dromagnoli/repos","events_url":"https://api.github.com/users/dromagnoli/events{/privacy}","received_events_url":"https://api.github.com/users/dromagnoli/received_events","type":"User","site_admin":false},"repo":{"id":4902433,"name":"geoserver","full_name":"dromagnoli/geoserver","owner":{"login":"dromagnoli","id":1278116,"avatar_url":"https://avatars3.githubusercontent.com/u/1278116?v=4","gravatar_id":"","url":"https://api.github.com/users/dromagnoli","html_url":"https://github.com/dromagnoli","followers_url":"https://api.github.com/users/dromagnoli/followers","following_url":"https://api.github.com/users/dromagnoli/following{/other_user}","gists_url":"https://api.github.com/users/dromagnoli/gists{/gist_id}","starred_url":"https://api.github.com/users/dromagnoli/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dromagnoli/subscriptions","organizations_url":"https://api.github.com/users/dromagnoli/orgs","repos_url":"https://api.github.com/users/dromagnoli/repos","events_url":"https://api.github.com/users/dromagnoli/events{/privacy}","received_events_url":"https://api.github.com/users/dromagnoli/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/dromagnoli/geoserver","description":"Mirror of central GeoServer subversion repository","fork":true,"url":"https://api.github.com/repos/dromagnoli/geoserver","forks_url":"https://api.github.com/repos/dromagnoli/geoserver/forks","keys_url":"https://api.github.com/repos/dromagnoli/geoserver/keys{/key_id}","collaborators_url":"https://api.github.com/repos/dromagnoli/geoserver/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/dromagnoli/geoserver/teams","hooks_url":"https://api.github.com/repos/dromagnoli/geoserver/hooks","issue_events_url":"https://api.github.com/repos/dromagnoli/geoserver/issues/events{/number}","events_url":"https://api.github.com/repos/dromagnoli/geoserver/events","assignees_url":"https://api.github.com/repos/dromagnoli/geoserver/assignees{/user}","branches_url":"https://api.github.com/repos/dromagnoli/geoserver/branches{/branch}","tags_url":"https://api.github.com/repos/dromagnoli/geoserver/tags","blobs_url":"https://api.github.com/repos/dromagnoli/geoserver/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/dromagnoli/geoserver/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/dromagnoli/geoserver/git/refs{/sha}","trees_url":"https://api.github.com/repos/dromagnoli/geoserver/git/trees{/sha}","statuses_url":"https://api.github.com/repos/dromagnoli/geoserver/statuses/{sha}","languages_url":"https://api.github.com/repos/dromagnoli/geoserver/languages","stargazers_url":"https://api.github.com/repos/dromagnoli/geoserver/stargazers","contributors_url":"https://api.github.com/repos/dromagnoli/geoserver/contributors","subscribers_url":"https://api.github.com/repos/dromagnoli/geoserver/subscribers","subscription_url":"https://api.github.com/repos/dromagnoli/geoserver/subscription","commits_url":"https://api.github.com/repos/dromagnoli/geoserver/commits{/sha}","git_commits_url":"https://api.github.com/repos/dromagnoli/geoserver/git/commits{/sha}","comments_url":"https://api.github.com/repos/dromagnoli/geoserver/comments{/number}","issue_comment_url":"https://api.github.com/repos/dromagnoli/geoserver/issues/comments{/number}","contents_url":"https://api.github.com/repos/dromagnoli/geoserver/contents/{+path}","compare_url":"https://api.github.com/repos/dromagnoli/geoserver/compare/{base}...{head}","merges_url":"https://api.github.com/repos/dromagnoli/geoserver/merges","archive_url":"https://api.github.com/repos/dromagnoli/geoserver/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/dromagnoli/geoserver/downloads","issues_url":"https://api.github.com/repos/dromagnoli/geoserver/issues{/number}","pulls_url":"https://api.github.com/repos/dromagnoli/geoserver/pulls{/number}","milestones_url":"https://api.github.com/repos/dromagnoli/geoserver/milestones{/number}","notifications_url":"https://api.github.com/repos/dromagnoli/geoserver/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/dromagnoli/geoserver/labels{/name}","releases_url":"https://api.github.com/repos/dromagnoli/geoserver/releases{/id}","deployments_url":"https://api.github.com/repos/dromagnoli/geoserver/deployments","created_at":"2012-07-05T09:18:40Z","updated_at":"2014-05-26T11:07:17Z","pushed_at":"2018-03-01T13:18:44Z","git_url":"git://github.com/dromagnoli/geoserver.git","ssh_url":"git@github.com:dromagnoli/geoserver.git","clone_url":"https://github.com/dromagnoli/geoserver.git","svn_url":"https://github.com/dromagnoli/geoserver","homepage":"http://geoserver.org","size":173251,"stargazers_count":1,"watchers_count":1,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":null,"forks":0,"open_issues":0,"watchers":1,"default_branch":"master"}},"base":{"label":"geoserver:master","ref":"master","sha":"309e251015313869a3473f3cb68e46f76ef19717","user":{"login":"geoserver","id":186522,"avatar_url":"https://avatars2.githubusercontent.com/u/186522?v=4","gravatar_id":"","url":"https://api.github.com/users/geoserver","html_url":"https://github.com/geoserver","followers_url":"https://api.github.com/users/geoserver/followers","following_url":"https://api.github.com/users/geoserver/following{/other_user}","gists_url":"https://api.github.com/users/geoserver/gists{/gist_id}","starred_url":"https://api.github.com/users/geoserver/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/geoserver/subscriptions","organizations_url":"https://api.github.com/users/geoserver/orgs","repos_url":"https://api.github.com/users/geoserver/repos","events_url":"https://api.github.com/users/geoserver/events{/privacy}","received_events_url":"https://api.github.com/users/geoserver/received_events","type":"Organization","site_admin":false},"repo":{"id":2751199,"name":"geoserver","full_name":"geoserver/geoserver","owner":{"login":"geoserver","id":186522,"avatar_url":"https://avatars2.githubusercontent.com/u/186522?v=4","gravatar_id":"","url":"https://api.github.com/users/geoserver","html_url":"https://github.com/geoserver","followers_url":"https://api.github.com/users/geoserver/followers","following_url":"https://api.github.com/users/geoserver/following{/other_user}","gists_url":"https://api.github.com/users/geoserver/gists{/gist_id}","starred_url":"https://api.github.com/users/geoserver/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/geoserver/subscriptions","organizations_url":"https://api.github.com/users/geoserver/orgs","repos_url":"https://api.github.com/users/geoserver/repos","events_url":"https://api.github.com/users/geoserver/events{/privacy}","received_events_url":"https://api.github.com/users/geoserver/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/geoserver/geoserver","description":"Official GeoServer repository","fork":false,"url":"https://api.github.com/repos/geoserver/geoserver","forks_url":"https://api.github.com/repos/geoserver/geoserver/forks","keys_url":"https://api.github.com/repos/geoserver/geoserver/keys{/key_id}","collaborators_url":"https://api.github.com/repos/geoserver/geoserver/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/geoserver/geoserver/teams","hooks_url":"https://api.github.com/repos/geoserver/geoserver/hooks","issue_events_url":"https://api.github.com/repos/geoserver/geoserver/issues/events{/number}","events_url":"https://api.github.com/repos/geoserver/geoserver/events","assignees_url":"https://api.github.com/repos/geoserver/geoserver/assignees{/user}","branches_url":"https://api.github.com/repos/geoserver/geoserver/branches{/branch}","tags_url":"https://api.github.com/repos/geoserver/geoserver/tags","blobs_url":"https://api.github.com/repos/geoserver/geoserver/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/geoserver/geoserver/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/geoserver/geoserver/git/refs{/sha}","trees_url":"https://api.github.com/repos/geoserver/geoserver/git/trees{/sha}","statuses_url":"https://api.github.com/repos/geoserver/geoserver/statuses/{sha}","languages_url":"https://api.github.com/repos/geoserver/geoserver/languages","stargazers_url":"https://api.github.com/repos/geoserver/geoserver/stargazers","contributors_url":"https://api.github.com/repos/geoserver/geoserver/contributors","subscribers_url":"https://api.github.com/repos/geoserver/geoserver/subscribers","subscription_url":"https://api.github.com/repos/geoserver/geoserver/subscription","commits_url":"https://api.github.com/repos/geoserver/geoserver/commits{/sha}","git_commits_url":"https://api.github.com/repos/geoserver/geoserver/git/commits{/sha}","comments_url":"https://api.github.com/repos/geoserver/geoserver/comments{/number}","issue_comment_url":"https://api.github.com/repos/geoserver/geoserver/issues/comments{/number}","contents_url":"https://api.github.com/repos/geoserver/geoserver/contents/{+path}","compare_url":"https://api.github.com/repos/geoserver/geoserver/compare/{base}...{head}","merges_url":"https://api.github.com/repos/geoserver/geoserver/merges","archive_url":"https://api.github.com/repos/geoserver/geoserver/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/geoserver/geoserver/downloads","issues_url":"https://api.github.com/repos/geoserver/geoserver/issues{/number}","pulls_url":"https://api.github.com/repos/geoserver/geoserver/pulls{/number}","milestones_url":"https://api.github.com/repos/geoserver/geoserver/milestones{/number}","notifications_url":"https://api.github.com/repos/geoserver/geoserver/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/geoserver/geoserver/labels{/name}","releases_url":"https://api.github.com/repos/geoserver/geoserver/releases{/id}","deployments_url":"https://api.github.com/repos/geoserver/geoserver/deployments","created_at":"2011-11-10T19:56:06Z","updated_at":"2018-02-28T00:00:30Z","pushed_at":"2018-03-02T00:30:17Z","git_url":"git://github.com/geoserver/geoserver.git","ssh_url":"git@github.com:geoserver/geoserver.git","clone_url":"https://github.com/geoserver/geoserver.git","svn_url":"https://github.com/geoserver/geoserver","homepage":"http://geoserver.org/","size":184200,"stargazers_count":1081,"watchers_count":1081,"language":"Java","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1011,"mirror_url":null,"archived":false,"open_issues_count":8,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":1011,"open_issues":8,"watchers":1081,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/geoserver/geoserver/pulls/2772"},"html":{"href":"https://github.com/geoserver/geoserver/pull/2772"},"issue":{"href":"https://api.github.com/repos/geoserver/geoserver/issues/2772"},"comments":{"href":"https://api.github.com/repos/geoserver/geoserver/issues/2772/comments"},"review_comments":{"href":"https://api.github.com/repos/geoserver/geoserver/pulls/2772/comments"},"review_comment":{"href":"https://api.github.com/repos/geoserver/geoserver/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/geoserver/geoserver/pulls/2772/commits"},"statuses":{"href":"https://api.github.com/repos/geoserver/geoserver/statuses/55d063cf1a0eeda5cc6fcf6e1f5fab54ebabacfa"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"aaime"}}