Как устроен Google Maps Данное описание предназначено прежде всего для программистов, желающих использовать снимки Google Maps в своих проектах. Здесь пойдет речь именно о снимках земной поверхности, а не о картах. Сразу предупреждаю, что в статье встречаются всякие там математические формулы. Поэтому если Вы не дружите с математикой, читать не рекомендую. Итак, каким же образом организовано хранение снимков и доступ к ним в сервисе Google Maps?
Вначале определим понятие уровня детализации. Уровень детализации (Level) - это "зум", он задается ползунком на страничке Google Maps. У ползунка есть 20 позиций. Будем считать, что нижняя позиция ползунка - это 1-й уровень (самый общий план Земли), верхняя позиция - 20-й уровень (максимальный зум).
Поверхность Земли отображается на плоскость в квадратный Битмап. Размеры Битмапа зависят от выбранного уровня детализации. Битмап разделен на квадратные блоки размером 256x256 пикселов.
Битмап первого уровня имеет размеры 256x256 пикселов, и таким образом, состоит всего лишь из одного блока. Имя этого блока - t. (Рисунки здесь приведены в уменьшенном виде.)
Битмап второго уровня имеет размеры 512x512 пикселов, он состоит из 2 x 2 = 4 блоков, которые имеют названия tq, tr, tt, ts.
Битмап третьего уровня имеет размеры 1024x1024 пискелов, он состоит уже из 4 x 4 = 16 блоков:
И т.д. до 20-го уровня.
Принцип, по которому образуются имена блоков, следующий. Если на N-ом уровне некоторый блок имеет имя [name], то на (N+1)-ом уровне он "режется" на 4 блока, которые получают имена [name]q, [name]r, [name]t, [name]s в таком порядке:
Еще раз отметим, что на любом уровне детализации блоки имеют размер 256x256 пикселов. С увеличением уровня количество блоков, а следовательно, и размеры Битмапа увеличиваются.
Когда на сайте Google Maps Вы при помощи ползунка выбираете уровень детализации, в окошке Вы видите фрагмент Битмапа указанного уровня. Те блоки, которые попали в окошко, постепенно подкачиваются браузером с сайта, и таким образом формируется изображение.
Введем следующие обозначения.
Code
NumTiles[Level] - количество блоков вдоль одной стороны Битмапа уровня Level:
NumTiles[Level] = 2 ^ (Level-1)
Code
NumTiles[1] = 1
NumTiles[2] = 2
NumTiles[3] = 4
NumTiles[4] = 8
NumTiles[5] = 16
NumTiles[6] = 32
NumTiles[7] = 64
NumTiles[8] = 128
NumTiles[9] = 256
NumTiles[10] = 512
NumTiles[11] = 1024
NumTiles[12] = 2048
NumTiles[13] = 4096
NumTiles[14] = 8192
NumTiles[15] = 16384
NumTiles[16] = 32768
NumTiles[17] = 65536
NumTiles[18] = 131072
NumTiles[19] = 262144
NumTiles[20] = 524288
BitmapSize[Level] - размер в пикселах одной стороны Битмапа уровня Level.
Code
BitmapSize[Level] = NumTiles[Level] * 256
BitmapOrigo[Level] - координаты (в пикселах) середины Битмапа уровня Level.
Code
BitmapOrigo[Level] = BitmapSize[Level] / 2
Теперь подробнее о преобразовании координат.
На Битмапе координаты (в пикселах) отсчитываются от левого верхнего угла. Левый верхний пискел имеет координаты (0; 0).
Все меридианы на Битмапе параллельны оси Y, все параллели идут вдоль оси X. Экватор проходит ровно посередине. Нулевой меридиан - тоже ровно посередине. Таким образом, точка с географическими координатами 0° широты, 0° долготы отображается на Битмап уровня Level в точку с координатами X = Y = BitmapOrigo[Level].
Принято следующее правило: широты южнее экватора считаются отрицательными, севернее экватора - положительными. Долготы к западу от нулевого меридиана - отрицательные, к востоку - положительные. По горизонтали Битмап охватывает весь диапазон долгот: от -180° до +180°. По вертикали - примерно от -85° до +85° (более подробно об этом будет сказано ниже).
Пусть заданы географические координаты точки на Земле: Lat - широта, Lon - долгота (в градусах). На Битмапе уровня Level она отображается в точку с координатами
Code
X = floor (BitmapOrigo[Level] + Lon * PixelsPerLonDegree[Level]);
Y = floor (BitmapOrigo[Level] - 0.5 * ln((1+z)/(1-z)) * PixelsPerLonRadian[Level]),
где z = sin (Lat).
Floor - округление до ближайшего целого в меньшую сторону, например:
Code
Floor(-2.8) = -3
Floor(2.8) = 2
Floor(-1.0) = -1
ln - натуральный логарифм. sin - синус. Заметим, что практически во всех компиляторах на вход синусу надо подавать значение угла в радианах, поэтому Lat надо из градусов перевести в радианы.
PixelsPerLonDegree[Level] - количество пикселов, приходящееся на один градус долготы на Битмапе уровня Level.
Code
PixelsPerLonDegree[Level] = BitmapSize[Level] / 360
Соответственно, PixelsPerLonRadian[Level] - количество пикселов, приходящееся на один радиан долготы.
Code
PixelsPerLonRadian[Level] = BitmapSize[Level] / (2*PI)
Из формул видно, что преобразование между X и Lon линейно. Это означает, что если взять меридианы, расположенные через фиксированное расстояние (в градусах) друг от друга, то и на Битмапе они будут отстоять друг от друга на одинаковое число пикселов. (Поэтому величины PixelsPerLonDegree[Level] и PixelsPerLonRadian[Level] являются фиксированными при конкретном значении Level).
Для преобразования между Y и Lat это неверно. Если взять параллели, расположенные через фиксированное расстояние (в градусах) друг от друга, то на Битмапе они будут расположены "гуще" вблизи экватора и "реже" вблизи полюсов.
Обратное преобразование выполняется по формулам
Code
Lon = (X - BitmapOrigo[Level]) / PixelsPerLonDegree[Level];
Lat = (2 * arctan(exp(z)) - PI/2) * 180/PI,
где z = (Y - BitmapOrigo[Level]) / -PixelsPerLonRadian[Level];
exp(z) - e в степени z.
arctan - арктангенс в радианах.
Далее, как все это использовать.
Пусть дана точка на поверхности Земли с географическими координатами широта=Lat, долгота=Lon. Мы хотим получить блок уровня Level, в который попадает эта точка.
1. По указанным выше формулам получаем координаты точки на Битмапе - X, Y.
2. Получаем номера (по горизонтали и вертикали) блока, в который попадает эта точка:
Code
NumX = floor (X / BlockSize);
NumY = floor (Y / BlockSize);
где BlockSize = 256 (размер стороны блока в пикселах). Блоки на Битмапе также нумеруются от левого верхнего угла. Левый верхний блок имеет номер (0; 0).
3. Зная Level и номер блока, можно получить его название. Здесь приведем процедуру на Паскале, аналогичную процедуре из скриптов Google.
Code
function GetBlockName (NumX, NumY: integer; Level: integer): string;
var
d: integer;
i: integer;
res: string;
begin
d:= round (power (2, Level-1));
if (NumX < 0) or (d-1 < NumX) then
begin
NumX:= NumX mod d;
if (NumX < 0) then NumX:= NumX + d;
end;
res:= 't';
for i:= 2 to Level do
begin
d:= d div 2;
if (NumY < d) then
begin
if (NumX < d)
then res:= res + 'q'
else begin res:= res + 'r'; NumX:= NumX - d; end;
end
else
begin
if (NumX < d)
then res:= res + 't'
else begin res:= res + 's'; NumX:= NumX - d; end;
NumY:= NumY - d;
end;
end;
Result:= res;
end;
Все снимки хранятся на Google Maps именно в виде таких блоков размером 256x256 пикселов. Каждый блок представляет собой jpeg-файл. Самих Битмапов нигде нет, это виртуальные объекты. Чтобы получить с сайта блок с именем [name], нужно задать следующий запрос:
http://kh0.google.com/kh?n=404&v=10&t=[name]
В имени блока буквы t, q, r, s должны быть строчными (не заглавными).
При просмотре сайта Google Maps браузером скрипты скачивают блоки попеременно с четырех хостов:
kh0.google.com,
kh1.google.com,
kh2.google.com,
kh3.google.com.
В более ранних версиях скриптов блоки скачивались с одного хоста kh.google.com; сейчас он-по прежнему доступен. Все пять хостов соответствуют одному IP-адресу и выдают абсолютно одинаковые картинки. В запросе присутствует параметр n=404. Если его убрать, то при попытке получить несуществующий блок Вы получите следующую картинку:
Если же этот параметр указан, то при запросе несуществующего блока хост выдает HTTP-ошибку 404 ("Документ не найден").
Параметр v=10 указывает на "версию" снимков. Периодически скрипты Google Maps переходят на очередную версию, и эта величина увеличивается на единицу. Переход на очередную версию, как правило, связан с добавлением снимков более высоких уровней детализации для некоторых территорий. Старые же снимки остаются неизменными.
Как уже упоминалось, максимальный доступный на Google Maps уровень детализации - 20-й. Однако снимки 20-го уровня есть не для всей поверхности Земли. С таким высоким зумом отсняты в основном крупные города. Разные области Земли покрыты с различной степенью детализации.
Кроме того, некоторые блоки высокого уровня получены "нечестным" путем - простым увеличением блоков более низкого уровня. В результате получается "размазанная" картинка. Вот пример - небольшая территория покрыта блоками 16-го уровня. Часть из них - нормальные снимки, а другая часть получена "размазыванием":
Средний размер jpeg-блоков составляет примерно 11 Кб. Битмап, например, 18-го уровня состоит из 1310722 = 17179869184 блоков. Таким образом, теоретический суммарный объем всех jpeg-блоков 18-го уровня составит порядка 176 Тбайт (180 тысяч Гбайт). "Теоретический" - потому что, как уже упоминалось, блоки 18-го уровня есть не для всей поверхности Земли. Я это все к тому, что если у кого-то возникают мысли "закачать весь Гугол к себе", то ему стоит призадуматься, а хватит ли у него денег на такое количество винчестеров...
Может возникнуть следующий вопрос: а какой масштаб имеет изображение на каждом конкретном уровне детализации? Ответ таков: изображение не имеет фиксированного масштаба. Для Битмапа уровня Level фиксированной является величина PixelsPerLonDegree[Level] - количество пикселов, приходящееся на один градус долготы (т.е. если отмерять этот градус вдоль географической параллели). Но поскольку на разных широтах один градус долготы покрывает различные расстояния (в метрах), то и масштаб будет меняться в зависимости от широты. Например, нижняя сторона блока trt в метрах окажется длиннее, чем его верхняя сторона. Укажем эту зависимость.
На широте fi радиус параллели составляет
Code
r = R * cos (fi)
где R - радиус Земли.
Длина дуги выражается через угол в радианах и радиус:
Code
l = alpha * r = alpha * R * cos (fi)
Следовательно, на широте fi масштаб картинки (уровня Level) по горизонтали составляет
Code
PixelsPerLonRadian[Level] / (R * cos (fi)) пикс/м
Если же отсчитывать градус вдоль меридиана, то тут еще сложнее. Масштаб картинки по вертикали непрерывно меняется с широтой. Единственное, что можно отметить, преобразование между географическими координатами и координатами на Битмапе выбрано таким образом, чтобы пропорции бесконечно малых объектов сохранялись. Это означает, что если на Земле стоит квадратный дом, то и на снимке он должен быть квадратным (хотя бы приблизительно), а не вытянутым по горизонтали или вертикали, иначе ценность таких изображений была бы сомнительной. Т.е. если выбрать какую-нибудь точку, то в малой ее окрестности масштаб картинки по горизонтали и по вертикали будет одинаков. Стоит сдвинуться чуть вверх или вниз - масштаб (и по горизонтали, и по вертикали) немного изменится.
При движении от экватора к полюсам на каждый пиксел приходится все меньше и меньше градусов (и метров). А значит, и в один блок по вертикали "влезает" все меньшее расстояние. Поскольку число блоков ограничено (NumTiles[Level]), то блоки не "доходят" до полюса, а "останавливаются" на широте 85.0511287798066°. Это число можно получить, подставив в формулу обратного преобразования (для Lat) значение Y = 0.
С учетом всего вышесказанного должно быть понятно, что блок вблизи экватора охватывает бОльшую территорию, чем блок того же уровня вблизи полюса. Т.е. ближе к полюсам разрешение блоков возрастает.
Сылка на картинку, отображаемую в окошке, (Link to this page) имеет вид
http://maps.google.com/maps?ll=55.751463,37.617273&spn=0.006493,0.031637&t=k&hl=en
Ее основными параметрами являются:
ll - широта и долгота центра картинки (от английских слов latitude - широта, longitude - долгота);
spn - размеры территории, которая видна в окошке - по горизонтали и вертикали, в градусах (от английского слова span - небольшой участок, объем, размах, диапазон).
Оригинал