Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

find-a-church: Make proximity controls zoom the map #154

Open
timblack1 opened this issue Jan 20, 2016 · 10 comments
Open

find-a-church: Make proximity controls zoom the map #154

timblack1 opened this issue Jan 20, 2016 · 10 comments
Milestone

Comments

@timblack1
Copy link
Owner

timblack1 commented Jan 20, 2016

Plan A

Maaayyybe an easy solution would be to:

  1. Define a circle in these terms: it is centered on the map's center, and has the user's selected radius in miles/km.
  2. Find all congregations which fit within that circle. The Great Circle Formula would allow us to do this calculation, if we fed it the circle's center point and the congregation's location.
  3. Call Map.fitToMarkers() or set that property. That will zoom the map to the closest view which includes the matching congregations.

And you're done!

Note: One motivation for this method is that it appears Google's Maps API's zoom levels only exist in discrete integers, and can't actually be set in finer (fractional or decimal) increments.

Note: This implies when the user selects a search radius, we need to hide all the congregations which don't fit in that radius, and more importantly, when the user pans or zooms the map, we need to SHOW all the new congregations which fit within the map's new bounds, and stop using the circle as the bounds within which to display congregations. In other words, the map needs two modes here:

  1. search radius/circle mode, and
  2. panning-zooming/bounding box mode.

Plan B

Or, if we want to be all complicated like we used to think we had to, we could try any of the following:

It looks like the zoom levels don't correspond directly to miles. See http://gis.stackexchange.com/questions/7430/what-ratio-scales-do-google-maps-zoom-levels-correspond-to, http://stackoverflow.com/questions/6048975/google-maps-v3-how-to-calculate-the-zoom-level-for-a-given-bounds for information on how to translate from miles to zoom levels.

Some example algorithms:

http://jeffjason.com/2011/12/google-maps-radius-to-zoom/
http://stackoverflow.com/questions/5162950/google-maps-api-v3-set-zoom-level-to-show-a-given-radius

Note: Google Maps uses the "Google Web Mercator" projection and coordinate system. Its official EPSG identifier is EPSG:3857. It uses spherical rather than the standard Mercator ellipsoid formulae. According to https://en.wikipedia.org/wiki/Web_Mercator#Properties, "While the Web Mercator's formulas are for the spherical form of the Mercator, geographical coordinates are required to be in the WGS 84 ellipsoidal datum." Formulae are available at https://en.wikipedia.org/wiki/Web_Mercator#Formulas. (Question: Do x & y values there refer to the bounding box's height & width in pixels?) Note there are a couple more formulae in a JavaScript-like language at the end of this article: https://alastaira.wordpress.com/2011/01/23/the-google-maps-bing-maps-spherical-mercator-projection/.

See general Google Maps coordinate overview at https://developers.google.com/maps/documentation/javascript/maptypes?csw=1#MapCoordinates.

This might work:

When a user changes the search radius (in miles/km):

  1. Find the distance between the current viewport's bounding box's center and a point on the box's edge directly to the left or right of that center point. This is the search radius currently displayed on the map.
  • Use the map.getBounds() function to get the latitude and longitude of the map's corners.
  • To find the left or right point, see if Google Maps provides an API. If not, then find the point halfway between the top and bottom corners of the box on that side. What is tricky here is to know what units to use to define that halfway point: degrees, radians, or miles.
  1. Get the map's current zoom level from the Google Maps API.
  2. Use a Google Web Mercator (perhaps identical to a formula for Universal Transverse Mercator) mileage conversion formula to calculate a new zoom level for the map.

Here is a Great Circle Distance formula: http://stackoverflow.com/questions/3525670/radius-of-viewable-region-in-google-maps-v3.

@timblack1 timblack1 added this to the 0.1 milestone Jan 20, 2016
@timblack1
Copy link
Owner Author

timblack1 commented Jul 14, 2016

Doug, I'm going to leave my notes here for you so you can work on the algebraic refactoring when you have time.

We start with this:

ratio_milesperzoomlevel = dis/zoom

Our goal: get new zoom level

So it seems this would be the right formula:

zoom * ratio_milesperzoomlevel = zoom * dis/zoom
zoom = dis/ratio_milesperzoomlevel

But that doesn't work, because according to docs describing Google Maps, when the zoom goes up linearly, miles go down quadratically, as is represented in the following table.

zoom miles divide by
0 24000 1
1 12000 2
2 6000 4
3 3000 8
4 1500 16
5 750
6 375
7 165

So the above table can be represented by the following formula.
How can we factor out z to be the only term on the left side of
the equation?

new_radius_miles = original_radius_miles/(2^z)
(2^z)*new_radius_miles = original_radius_miles
(2^z) = original_radius_miles/new_radius_miles

TODO: Start here. How can we factor out z?

z = ?

The following might work:

(2^new_zoom) = original_radius_miles/new_radius_miles

We can factor out new_zoom using the following formula:
For a = b^c, c = log b of a, or c = Math.log(a) / Math.log(b)

var new_zoom = Math.log(dis/Number(this.search_radius)) / Math.LN2;

@timblack1
Copy link
Owner Author

timblack1 commented Jul 17, 2016

@timblack1
Copy link
Owner Author

timblack1 commented Jul 19, 2016

Try:

(2^z) = original_radius_miles/new_radius_miles
// For a = b^c, c = log b of a, or c = Math.log(a) / Math.log(b)
z = Math.log10(original_radius_miles/new_radius_miles) / Math.log10(2)

@timblack1
Copy link
Owner Author

timblack1 commented Sep 30, 2016

This is the order in which the user's interaction works. First, the interface has a zoom level. Then the user selects a new search radius (in miles), which moves the user's selection up or down the rows of the table below. We need to calculate the new zoom level which results.

miles zoom divide original miles by (shows quadratic progression)
24000 0 1
12000 1 2
6000 2 4
3000 3 8
1500 4 16
750 5 32
375 6 64
187.5 7 128

When miles go down by 1/2, zoom goes up by 1. When miles go up by 2x, zoom goes down by 1. We need to translate this into a function which takes in a new number of miles selected by the user, and outputs a new zoom level.

newMiles = oldMiles/(2^(newZoom - oldZoom))
(2^(newZoom - oldZoom)) * newMiles = oldMiles
2^(newZoom - oldZoom) = oldMiles/newMiles
2^(newZoom - oldZoom) = oldMiles/newMiles
log_2(oldMiles/newMiles) = newZoom - oldZoom
log_2(oldMiles/newMiles) + oldZoom = newZoom

Check out the Intro to Logarithms at https://www.khanacademy.org/math/algebra-home/alg-exp-and-log.

@timblack1
Copy link
Owner Author

We've implemented this partially, and paused development until we hear back from GoogleWebComponents/google-map#339 as to whether we can use non-integer zoom values. For now we just round search radii to the nearest integer zoom level, which is not as fine a resolution as we would like. We did this so we could merge the feature branch and not let it get far behind the develop branch. We hope to come back to this later.

@timblack1
Copy link
Owner Author

Problem: Our current algorithm for calculating the zoom level seems to be correct, so long as the map's pixel dimensions remain the same. If the user resizes the window, making the map change pixel dimensions, then the calculations are not accurate anymore.

This raises the question, is the map's initial zoom level correctly coordinated with the initial search radius entered in the search radius input? If so, how can we be sure it will be correct on different screen sizes? If not, then the algorithm may be incorrect.

@timblack1
Copy link
Owner Author

timblack1 commented Feb 17, 2017

Here's a recommendation for how to get the right algorithm:

  1. Find out which map projection Google Maps uses. For example, the "Universal Transverse Mercator" projection.
  2. Find a standard equation for that projection which will permit us to calculate what we need. The equation might be called a "mileage conversion formula."

@DouglasHuston
Copy link
Contributor

http://stackoverflow.com/questions/2656580/common-mercator-projection-formulas-for-google-maps-not-working-correctly

If the viewing area is defined in Lat/Lng format, The distortion happens when it is populated with the appropriate Mercator projections. Instead, the correct viewing box must be defined in Mercator or Project Mercator.

@timblack1
Copy link
Owner Author

@DouglasHuston, is the point of your last comment on here (at #154 (comment)) that we need to use the Mercator projection?

@DouglasHuston
Copy link
Contributor

If we do use the Mercator projection, we must define the viewing box correctly, and not just use the Lat/Lng format. This does not mean we are restricted to only using the Mercator projection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants