{"action":"created","comment":{"url":"https://api.github.com/repos/mapzen/TileStache/pulls/comments/42899913","id":42899913,"diff_hunk":"@@ -1181,12 +1185,86 @@ def _make_new_properties(props, props_instructions):\n \n     return new_props\n \n+\n+def _snap_to_grid(shape, grid_size):\n+    \"\"\"\n+    Snap coordinates of a shape to a multiple of `grid_size`.\n+\n+    This can be useful when there's some error in point\n+    positions, but we're using an algorithm which is very\n+    sensitive to coordinate exactness. For example, when\n+    calculating the boundary of several items, it makes a\n+    big difference whether the shapes touch or there's a\n+    very small gap between them.\n+\n+    This is implemented here because it doesn't exist in\n+    GEOS or Shapely. It exists in PostGIS, but only because\n+    it's implemented there as well. Seems like it would be a\n+    useful thing to have in GEOS, though.\n+\n+    >>> _snap_to_grid(Point(0.5, 0.5), 1).wkt\n+    'POINT (1 1)'\n+    >>> _snap_to_grid(Point(0.1, 0.1), 1).wkt\n+    'POINT (0 0)'\n+    >>> _snap_to_grid(Point(-0.1, -0.1), 1).wkt\n+    'POINT (-0 -0)'\n+    >>> _snap_to_grid(LineString([(1.1,1.1),(1.9,0.9)]), 1).wkt\n+    'LINESTRING (1 1, 2 1)'\n+    _snap_to_grid(Polygon([(0.1,0.1),(3.1,0.1),(3.1,3.1),(0.1,3.1),(0.1,0.1)],[[(1.1,0.9),(1.1,1.9),(2.1,1.9),(2.1,0.9),(1.1,0.9)]]), 1).wkt\n+    'POLYGON ((0 0, 3 0, 3 3, 0 3, 0 0), (1 1, 1 2, 2 2, 2 1, 1 1))'\n+    >>> _snap_to_grid(MultiPoint([Point(0.1, 0.1), Point(0.9, 0.9)]), 1).wkt\n+    'MULTIPOINT (0 0, 1 1)'\n+    >>> _snap_to_grid(MultiLineString([LineString([(0.1, 0.1), (0.9, 0.9)]), LineString([(0.9, 0.1),(0.1,0.9)])]), 1).wkt\n+    'MULTILINESTRING ((0 0, 1 1), (1 0, 0 1))'\n+    \"\"\"\n+\n+    # snap a single coordinate value\n+    def _snap(c):\n+        return grid_size * round(c / grid_size, 0)\n+\n+    # snap all coordinate pairs in something iterable\n+    def _snap_coords(c):\n+        return [(_snap(x), _snap(y)) for x, y in c]\n+\n+    # recursively snap all coordinates in an iterable over\n+    # geometries.\n+    def _snap_multi(geoms):\n+        return [_snap_to_grid(g, grid_size) for g in geoms]\n+\n+    shape_type = shape.geom_type\n+    if shape_type == 'Point':\n+        return Point(_snap(shape.x), _snap(shape.y))\n+\n+    elif shape_type == 'LineString':\n+        return LineString(_snap_coords(shape.coords))\n+\n+    elif shape_type == 'Polygon':\n+        exterior = LinearRing(_snap_coords(shape.exterior.coords))\n+        interiors = []\n+        for interior in shape.interiors:\n+            interiors.append(LinearRing(_snap_coords(interior.coords)))\n+        return Polygon(exterior, interiors)\n+\n+    elif shape_type == 'MultiPoint':\n+        return MultiPoint(_snap_multi(shape.geoms))\n+\n+    elif shape_type == 'MultiLineString':\n+        return MultiLineString(_snap_multi(shape.geoms))\n+\n+    elif shape_type == 'MultiPolygon':\n+        return MultiPolygon(_snap_multi(shape.geoms))","path":"TileStache/Goodies/VecTiles/transform.py","position":82,"original_position":82,"commit_id":"f35a65195e198eac350b0206ba32bec8cabad82c","original_commit_id":"f35a65195e198eac350b0206ba32bec8cabad82c","user":{"login":"zerebubuth","id":271360,"avatar_url":"https://avatars.githubusercontent.com/u/271360?v=3","gravatar_id":"","url":"https://api.github.com/users/zerebubuth","html_url":"https://github.com/zerebubuth","followers_url":"https://api.github.com/users/zerebubuth/followers","following_url":"https://api.github.com/users/zerebubuth/following{/other_user}","gists_url":"https://api.github.com/users/zerebubuth/gists{/gist_id}","starred_url":"https://api.github.com/users/zerebubuth/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zerebubuth/subscriptions","organizations_url":"https://api.github.com/users/zerebubuth/orgs","repos_url":"https://api.github.com/users/zerebubuth/repos","events_url":"https://api.github.com/users/zerebubuth/events{/privacy}","received_events_url":"https://api.github.com/users/zerebubuth/received_events","type":"User","site_admin":false},"body":"There's a couple of issues with GeometryCollections - first, we shouldn't have them at the point this function is called, as there's a check first to see if it's a Polygon or MultiPolygon. I did write the code to be generic, but... Second, found that in v1.4.3 of Shapely, there's [no way to instantiate a GeometryCollection with geometries](https://github.com/Toblerity/Shapely/blob/1.4.3/shapely/geometry/collection.py#L18). I would create it and then set `geoms`, but I got `http://lists.gispython.org/pipermail/community/2012-November/003156.html`. It seems that it's [intentionally not supported](http://lists.gispython.org/pipermail/community/2012-November/003156.html).","created_at":"2015-10-23T18:43:47Z","updated_at":"2015-10-23T18:43:47Z","html_url":"https://github.com/mapzen/TileStache/pull/76#discussion_r42899913","pull_request_url":"https://api.github.com/repos/mapzen/TileStache/pulls/76","_links":{"self":{"href":"https://api.github.com/repos/mapzen/TileStache/pulls/comments/42899913"},"html":{"href":"https://github.com/mapzen/TileStache/pull/76#discussion_r42899913"},"pull_request":{"href":"https://api.github.com/repos/mapzen/TileStache/pulls/76"}}},"pull_request":{"url":"https://api.github.com/repos/mapzen/TileStache/pulls/76","id":48600158,"html_url":"https://github.com/mapzen/TileStache/pull/76","diff_url":"https://github.com/mapzen/TileStache/pull/76.diff","patch_url":"https://github.com/mapzen/TileStache/pull/76.patch","issue_url":"https://api.github.com/repos/mapzen/TileStache/issues/76","number":76,"state":"open","locked":false,"title":"Fix water boundaries","user":{"login":"zerebubuth","id":271360,"avatar_url":"https://avatars.githubusercontent.com/u/271360?v=3","gravatar_id":"","url":"https://api.github.com/users/zerebubuth","html_url":"https://github.com/zerebubuth","followers_url":"https://api.github.com/users/zerebubuth/followers","following_url":"https://api.github.com/users/zerebubuth/following{/other_user}","gists_url":"https://api.github.com/users/zerebubuth/gists{/gist_id}","starred_url":"https://api.github.com/users/zerebubuth/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zerebubuth/subscriptions","organizations_url":"https://api.github.com/users/zerebubuth/orgs","repos_url":"https://api.github.com/users/zerebubuth/repos","events_url":"https://api.github.com/users/zerebubuth/events{/privacy}","received_events_url":"https://api.github.com/users/zerebubuth/received_events","type":"User","site_admin":false},"body":"Fix a couple of water boundaries issues. Well, one fix, one dirty hack-around.\r\n\r\n## Snapping to grid\r\n\r\nWe get our water / river polygons through osm2pgsql, but our ocean polygons via the osmcoastline shapefile download. The different processing done by each pipeline means that the numeric precision of the coordinates can be very, very slightly off (by about 1cm in this case). There's nothing we can really do about this - that level of precision is pretty good, given how the data is being processed and projected in the different pipelines.\r\n\r\nThe fix for this is to snap to grid before doing anything with the polygons. PostGIS has a `ST_SnapToGrid` function, but this isn't coming from GEOS, it's implemented by PostGIS. Likewise, Shapely doesn't have a snapping function, so this PR adds one.\r\n\r\n## Dirty hack around duplicate polygons\r\n\r\nWhen a way is a member of a multipolygon relation, the relation usually \"supersedes\" the way and suppresses its output. This _doesn't_ happen when the way has tags which aren't present on the relation for the very good reason that those tags might be important (e.g: for inner courtyards tagged as a different type of object).\r\n\r\nSince performing a union on all the polygons is a _very_ expensive operation, instead this PR introduces a method which appears to be effective and cheap: it compares the area of the geometry and any intersecting geometry and if they match _exactly_ then it treats that as a proxy for the object's identity and doesn't intersect with it. This appears to fix the Lake Merced case.","created_at":"2015-10-23T16:20:58Z","updated_at":"2015-10-23T18:43:47Z","closed_at":null,"merged_at":null,"merge_commit_sha":"a2cfc147b498957645421bda479ca8094621edf9","assignee":{"login":"rmarianski","id":53770,"avatar_url":"https://avatars.githubusercontent.com/u/53770?v=3","gravatar_id":"","url":"https://api.github.com/users/rmarianski","html_url":"https://github.com/rmarianski","followers_url":"https://api.github.com/users/rmarianski/followers","following_url":"https://api.github.com/users/rmarianski/following{/other_user}","gists_url":"https://api.github.com/users/rmarianski/gists{/gist_id}","starred_url":"https://api.github.com/users/rmarianski/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rmarianski/subscriptions","organizations_url":"https://api.github.com/users/rmarianski/orgs","repos_url":"https://api.github.com/users/rmarianski/repos","events_url":"https://api.github.com/users/rmarianski/events{/privacy}","received_events_url":"https://api.github.com/users/rmarianski/received_events","type":"User","site_admin":false},"milestone":null,"commits_url":"https://api.github.com/repos/mapzen/TileStache/pulls/76/commits","review_comments_url":"https://api.github.com/repos/mapzen/TileStache/pulls/76/comments","review_comment_url":"https://api.github.com/repos/mapzen/TileStache/pulls/comments{/number}","comments_url":"https://api.github.com/repos/mapzen/TileStache/issues/76/comments","statuses_url":"https://api.github.com/repos/mapzen/TileStache/statuses/f35a65195e198eac350b0206ba32bec8cabad82c","head":{"label":"mapzen:303-fix-water-boundaries","ref":"303-fix-water-boundaries","sha":"f35a65195e198eac350b0206ba32bec8cabad82c","user":{"login":"mapzen","id":5435747,"avatar_url":"https://avatars.githubusercontent.com/u/5435747?v=3","gravatar_id":"","url":"https://api.github.com/users/mapzen","html_url":"https://github.com/mapzen","followers_url":"https://api.github.com/users/mapzen/followers","following_url":"https://api.github.com/users/mapzen/following{/other_user}","gists_url":"https://api.github.com/users/mapzen/gists{/gist_id}","starred_url":"https://api.github.com/users/mapzen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mapzen/subscriptions","organizations_url":"https://api.github.com/users/mapzen/orgs","repos_url":"https://api.github.com/users/mapzen/repos","events_url":"https://api.github.com/users/mapzen/events{/privacy}","received_events_url":"https://api.github.com/users/mapzen/received_events","type":"Organization","site_admin":false},"repo":{"id":16214517,"name":"TileStache","full_name":"mapzen/TileStache","owner":{"login":"mapzen","id":5435747,"avatar_url":"https://avatars.githubusercontent.com/u/5435747?v=3","gravatar_id":"","url":"https://api.github.com/users/mapzen","html_url":"https://github.com/mapzen","followers_url":"https://api.github.com/users/mapzen/followers","following_url":"https://api.github.com/users/mapzen/following{/other_user}","gists_url":"https://api.github.com/users/mapzen/gists{/gist_id}","starred_url":"https://api.github.com/users/mapzen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mapzen/subscriptions","organizations_url":"https://api.github.com/users/mapzen/orgs","repos_url":"https://api.github.com/users/mapzen/repos","events_url":"https://api.github.com/users/mapzen/events{/privacy}","received_events_url":"https://api.github.com/users/mapzen/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mapzen/TileStache","description":"A stylish alternative for caching your map tiles.","fork":true,"url":"https://api.github.com/repos/mapzen/TileStache","forks_url":"https://api.github.com/repos/mapzen/TileStache/forks","keys_url":"https://api.github.com/repos/mapzen/TileStache/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mapzen/TileStache/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mapzen/TileStache/teams","hooks_url":"https://api.github.com/repos/mapzen/TileStache/hooks","issue_events_url":"https://api.github.com/repos/mapzen/TileStache/issues/events{/number}","events_url":"https://api.github.com/repos/mapzen/TileStache/events","assignees_url":"https://api.github.com/repos/mapzen/TileStache/assignees{/user}","branches_url":"https://api.github.com/repos/mapzen/TileStache/branches{/branch}","tags_url":"https://api.github.com/repos/mapzen/TileStache/tags","blobs_url":"https://api.github.com/repos/mapzen/TileStache/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mapzen/TileStache/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mapzen/TileStache/git/refs{/sha}","trees_url":"https://api.github.com/repos/mapzen/TileStache/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mapzen/TileStache/statuses/{sha}","languages_url":"https://api.github.com/repos/mapzen/TileStache/languages","stargazers_url":"https://api.github.com/repos/mapzen/TileStache/stargazers","contributors_url":"https://api.github.com/repos/mapzen/TileStache/contributors","subscribers_url":"https://api.github.com/repos/mapzen/TileStache/subscribers","subscription_url":"https://api.github.com/repos/mapzen/TileStache/subscription","commits_url":"https://api.github.com/repos/mapzen/TileStache/commits{/sha}","git_commits_url":"https://api.github.com/repos/mapzen/TileStache/git/commits{/sha}","comments_url":"https://api.github.com/repos/mapzen/TileStache/comments{/number}","issue_comment_url":"https://api.github.com/repos/mapzen/TileStache/issues/comments{/number}","contents_url":"https://api.github.com/repos/mapzen/TileStache/contents/{+path}","compare_url":"https://api.github.com/repos/mapzen/TileStache/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mapzen/TileStache/merges","archive_url":"https://api.github.com/repos/mapzen/TileStache/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mapzen/TileStache/downloads","issues_url":"https://api.github.com/repos/mapzen/TileStache/issues{/number}","pulls_url":"https://api.github.com/repos/mapzen/TileStache/pulls{/number}","milestones_url":"https://api.github.com/repos/mapzen/TileStache/milestones{/number}","notifications_url":"https://api.github.com/repos/mapzen/TileStache/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mapzen/TileStache/labels{/name}","releases_url":"https://api.github.com/repos/mapzen/TileStache/releases{/id}","created_at":"2014-01-24T19:28:51Z","updated_at":"2015-10-05T10:32:25Z","pushed_at":"2015-10-23T16:21:44Z","git_url":"git://github.com/mapzen/TileStache.git","ssh_url":"git@github.com:mapzen/TileStache.git","clone_url":"https://github.com/mapzen/TileStache.git","svn_url":"https://github.com/mapzen/TileStache","homepage":"http://tilestache.org","size":7032,"stargazers_count":14,"watchers_count":14,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":7,"mirror_url":null,"open_issues_count":3,"forks":7,"open_issues":3,"watchers":14,"default_branch":"integration-1"}},"base":{"label":"mapzen:integration-1","ref":"integration-1","sha":"28b965f09c44f0dbc15f52c11d35363705f7f419","user":{"login":"mapzen","id":5435747,"avatar_url":"https://avatars.githubusercontent.com/u/5435747?v=3","gravatar_id":"","url":"https://api.github.com/users/mapzen","html_url":"https://github.com/mapzen","followers_url":"https://api.github.com/users/mapzen/followers","following_url":"https://api.github.com/users/mapzen/following{/other_user}","gists_url":"https://api.github.com/users/mapzen/gists{/gist_id}","starred_url":"https://api.github.com/users/mapzen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mapzen/subscriptions","organizations_url":"https://api.github.com/users/mapzen/orgs","repos_url":"https://api.github.com/users/mapzen/repos","events_url":"https://api.github.com/users/mapzen/events{/privacy}","received_events_url":"https://api.github.com/users/mapzen/received_events","type":"Organization","site_admin":false},"repo":{"id":16214517,"name":"TileStache","full_name":"mapzen/TileStache","owner":{"login":"mapzen","id":5435747,"avatar_url":"https://avatars.githubusercontent.com/u/5435747?v=3","gravatar_id":"","url":"https://api.github.com/users/mapzen","html_url":"https://github.com/mapzen","followers_url":"https://api.github.com/users/mapzen/followers","following_url":"https://api.github.com/users/mapzen/following{/other_user}","gists_url":"https://api.github.com/users/mapzen/gists{/gist_id}","starred_url":"https://api.github.com/users/mapzen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mapzen/subscriptions","organizations_url":"https://api.github.com/users/mapzen/orgs","repos_url":"https://api.github.com/users/mapzen/repos","events_url":"https://api.github.com/users/mapzen/events{/privacy}","received_events_url":"https://api.github.com/users/mapzen/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/mapzen/TileStache","description":"A stylish alternative for caching your map tiles.","fork":true,"url":"https://api.github.com/repos/mapzen/TileStache","forks_url":"https://api.github.com/repos/mapzen/TileStache/forks","keys_url":"https://api.github.com/repos/mapzen/TileStache/keys{/key_id}","collaborators_url":"https://api.github.com/repos/mapzen/TileStache/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/mapzen/TileStache/teams","hooks_url":"https://api.github.com/repos/mapzen/TileStache/hooks","issue_events_url":"https://api.github.com/repos/mapzen/TileStache/issues/events{/number}","events_url":"https://api.github.com/repos/mapzen/TileStache/events","assignees_url":"https://api.github.com/repos/mapzen/TileStache/assignees{/user}","branches_url":"https://api.github.com/repos/mapzen/TileStache/branches{/branch}","tags_url":"https://api.github.com/repos/mapzen/TileStache/tags","blobs_url":"https://api.github.com/repos/mapzen/TileStache/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/mapzen/TileStache/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/mapzen/TileStache/git/refs{/sha}","trees_url":"https://api.github.com/repos/mapzen/TileStache/git/trees{/sha}","statuses_url":"https://api.github.com/repos/mapzen/TileStache/statuses/{sha}","languages_url":"https://api.github.com/repos/mapzen/TileStache/languages","stargazers_url":"https://api.github.com/repos/mapzen/TileStache/stargazers","contributors_url":"https://api.github.com/repos/mapzen/TileStache/contributors","subscribers_url":"https://api.github.com/repos/mapzen/TileStache/subscribers","subscription_url":"https://api.github.com/repos/mapzen/TileStache/subscription","commits_url":"https://api.github.com/repos/mapzen/TileStache/commits{/sha}","git_commits_url":"https://api.github.com/repos/mapzen/TileStache/git/commits{/sha}","comments_url":"https://api.github.com/repos/mapzen/TileStache/comments{/number}","issue_comment_url":"https://api.github.com/repos/mapzen/TileStache/issues/comments{/number}","contents_url":"https://api.github.com/repos/mapzen/TileStache/contents/{+path}","compare_url":"https://api.github.com/repos/mapzen/TileStache/compare/{base}...{head}","merges_url":"https://api.github.com/repos/mapzen/TileStache/merges","archive_url":"https://api.github.com/repos/mapzen/TileStache/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/mapzen/TileStache/downloads","issues_url":"https://api.github.com/repos/mapzen/TileStache/issues{/number}","pulls_url":"https://api.github.com/repos/mapzen/TileStache/pulls{/number}","milestones_url":"https://api.github.com/repos/mapzen/TileStache/milestones{/number}","notifications_url":"https://api.github.com/repos/mapzen/TileStache/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/mapzen/TileStache/labels{/name}","releases_url":"https://api.github.com/repos/mapzen/TileStache/releases{/id}","created_at":"2014-01-24T19:28:51Z","updated_at":"2015-10-05T10:32:25Z","pushed_at":"2015-10-23T16:21:44Z","git_url":"git://github.com/mapzen/TileStache.git","ssh_url":"git@github.com:mapzen/TileStache.git","clone_url":"https://github.com/mapzen/TileStache.git","svn_url":"https://github.com/mapzen/TileStache","homepage":"http://tilestache.org","size":7032,"stargazers_count":14,"watchers_count":14,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":7,"mirror_url":null,"open_issues_count":3,"forks":7,"open_issues":3,"watchers":14,"default_branch":"integration-1"}},"_links":{"self":{"href":"https://api.github.com/repos/mapzen/TileStache/pulls/76"},"html":{"href":"https://github.com/mapzen/TileStache/pull/76"},"issue":{"href":"https://api.github.com/repos/mapzen/TileStache/issues/76"},"comments":{"href":"https://api.github.com/repos/mapzen/TileStache/issues/76/comments"},"review_comments":{"href":"https://api.github.com/repos/mapzen/TileStache/pulls/76/comments"},"review_comment":{"href":"https://api.github.com/repos/mapzen/TileStache/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/mapzen/TileStache/pulls/76/commits"},"statuses":{"href":"https://api.github.com/repos/mapzen/TileStache/statuses/f35a65195e198eac350b0206ba32bec8cabad82c"}}}}