понедельник, 14 января 2013 г.

Let NAS to track your family

                                                  Доктор едет-едет сквозь снежную равнину
                                                  .........
                                                  Где ты, где ты, где ты - белая карета?
                                                  «Человек и кошка», Ноль

Есть множество сервисов, предоставляющих возможность сообщать кому-то о своем местонахождении. Но зачем барину чей-то сервис, если выпиливать он любит, а фанерка и лобзик всегда под рукой?

Причин для создания собственного варианта трекера можно назвать хотя бы несколько: свой функционал - сколько какого нужно, столько такого напилил, свой сервер - 0% загрузки + никому не придет в голову его ломать ради шкурок хомячков + доступен в локальной сети, монетизация в виде рекламы ему не грозит, гимнастика для повышения пластичности мозга, ..., профит.

Нет таких трудностей, которые мы не могли бы себе создать.



Карта с треком


Задача ставится таким образом:
  • архитектура - клиент-сервер, 
  • сервер - NAS+Apache+CGI
  • клиент - любой браузер (мобильный, не очень, совсем не мобильный)
  • ещё клиент - tasker приложение (простой сборщик координат и скоростей)
  • функционал сервера - сохранение координат клиента в течение сеанса и выдача полученной подборки по требованию
  • функционал клиента - отправка координат серверу (если возможно/необходимо) и/или просмотр трека (своего или чужого)
  • многопользовательская система (МПС - в следующей серии)

Клиенты

Ограничимся двумя картографическими сервисами, чтобы не поседеть окончательно, пока выпиливаем наш трекер. Пусть это будут Яндекс и Google. Оба они предоставляют API для JavaScript и возможности отобразить пользовательские данные в мобильных приложениях карт. API для разработки собственного Java приложения я не рассматриваю, т.к. "не умеем еще пока" (с) и это уж слишком - плодить еще одно картоприложение в смартфоне со своим кэшем карт и пр. Повторюсь, ниже описываю телодвижения для "коробочного" набора программ клиентских терминалов.

Мобильные

С Google Maps на смартфоне все получилось просто: в описании сказано "Укажите в окне поиска URL KML". Все. Работает на ура и "из коробки":


 Карта с треком описание точки Список точек трека

Конечно не удобно копировать адрес ссылки на скрипт из текстового файла (о_О) и куда-то там вставлять. Но мы-то знаем, что GM откликается на схему geo:// вида geo:0,0?q=business+near+city подставляем на место запроса наш URL и приложение бежит за нашим треком. Чтобы заполучить интент с этой схемой в теле, есть несколько вариантов финтов ушами. Мы делаем такой: создаем в браузере закладку на серверный же скрипт, который вернет браузеру заголовок Location: geo:0,0?q=http://my.site.org/cgi-bin/script.cgi?kmlg дальше выберем из списка предложенных программ обработчиков этого сообщения Google Maps. Кладем закладку в удобное место и пользуемся. Получается такой 302 редирект скриптом на самого себя в другой инкарнации.
В качестве другого варианта можете попробовать консольную утилиту am, с отправкой того же интента если у Вас Android или использовать Tasker (так удобнее - исключается первый запрос к серверу и работа браузера. Плюс - можно вкрутить в сценарий для Tasker).
По такому же принципу можно "найти" последнюю отмеченную точку наблюдаемого. Только в строке поиска сервер выдает координаты точки и небольшой текст для метки. (См. в разделе Сервер, как это сделать)


В Яндексе, видимо, решили не оставлять этакую дыру в безопасности приложения (?) и не доверились пользовательским данным поданным на вход напрямую (их ведь еще проверять нужно). Однако они там тоже не Schick'ом бриты и сделали свой язык:
YMapsML (Yandex Maps Markup Language) — XML-язык, предназначенный для описания географических данных на картах Яндекса.

Он может применяться как на "больших" Картах (см. ниже), так и в мобильных, правда в весьма урезанной версии и с абсолютным нулем достоверной документации для стороннего разработчика. В стародавние времена, разработчики анонсировали возможность создания пользовательских слоев в МЯК в виде виджетов. Можно только догадываться почему и в какую сторону этот функционал постоянно меняется и почему до потенциальных разработчиков виджетов не доходит никакой информации о том как и что делать. Видимо сыровато пока. Тем не менее, пока еще можно отобразить на карте точки, в которых наблюдаемый отметился:

Трек в МЯК
Массированные попытки применить свой стиль к точке и отобразить линию, не возымели... Посмотрим что будет дальше. Замечу лишь, что виджет в МЯК может быть довольно интерактивным: отправлять серверу местоположение клиента (чтобы одновременно с просмотром следящий клиент сам мог отметиться на сервере) и координаты границ карты (для ограничения количества получаемых с сервера точек, например), открывать веб-страницы (для настройки параметров виджета или регистрации на сервере - авторизацию на уровне веб-сервера пока не сделали).
Последнюю отмеченную точку так же можно посмотреть и в МЯК. Схему geo:// приложение теперь поддерживает. (См. в разделе Сервер, как это сделать)

Браузерные

В силу того, что новые API обоих картсервисов понимают KML в достаточной для нас мере, сделаем серверный скрипт выдающий точки в этом формате.
Также можно запрашивать у браузера местоположение клиента для отметки на сервере. Но это имеет смысл только в мобильном браузере, при наличии в устройстве достаточно точных провайдеров местоположения и в многопользовательской системе (не реализовано).

Google Maps
К сожалению GM представляет полученные в KML данные как единый объект и не предоставляет доступа к отдельным его элементам. Поэтому в этом варианте будем грузить с сервера еще и линию трека.
Пусть сервер отдает браузеру HTML страничку с незатейливым JS-кодом, подгружающим карточное API и наш трек:

<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0px; padding: 0px }
#map_canvas { height: 100% }
</style>
<script type="text/javascript" src="https://maps.google.com/maps/api/js?sensor=true&region=RU"> </script>
<script type="text/javascript">
function initialize() {
geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng(55.88, 37.62); // начальное позиционирование карты (после загрузки KML карта панорамируется на него автоматически, если
это не отключить)
var myOptions = {
zoom: 12,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP,
};
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var trafficLayer = new google.maps.TrafficLayer(); // Пробки приходится учитывать
trafficLayer.setMap(map);
// var bikeLayer = new google.maps.BicyclingLayer();
// у Вас есть велодорожки???
// bikeLayer.setMap(map);
  var ctaLayer = new google.maps.KmlLayer('http://my.site.org/cgi-bin/script.cgi?kmlg');
ctaLayer.setMap(map);
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas" style="width:100%; height:100%"></div>
</body>
</html>

Google Maps с треком

Клик по линии покажет описание с датой и именем, например. Минимум элементов управления стоит по умолчанию. Управление их набором и видом - через API. Стили иконок точек и линий изменяются, при желании (сделаем в МПС).


Яндекс.Карты
Доступ к отдельным элементам загруженного KML дает нам возможность творить. Документация, конечно, рассчитана на нашего человека, который умеет читать между строк, страниц и примеров. А также любит творить, выдумывать и пробовать.
Принцип тот же - браузеру отдается HTML с загрузкой API и трека в виде KML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>V.Oz. on the road</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0px; padding: 0px }
#map { height: 100% }
</style>
<script src="http://api-maps.yandex.ru/2.0-stable/?load=package.full&lang=ru-RU" type="text/javascript"></script>
<script type="text/javascript">
window.onload = function () {
ymaps.ready(function() {
// Схема
var myMap = new ymaps.Map('map', {
center: [55.88, 37.62],
zoom: 12,
behaviors: ['default', 'scrollZoom']
});
var clusterer = new ymaps.Clusterer({clusterDisableClickZoom: true}); // группировка точек на малых масштабах или в местах скопления
var track = new ymaps.GeoObject({geometry: { type: 'LineString'}}, {strokeWidth: 3/*, strokeColor: */});

myMap.controls.add('zoomControl')
.add('mapTools')
.add('scaleLine')
.add('trafficControl');
  ymaps.geoXml.load("http://yandex:pass@my.site.org/cgi-bin/script.cgi?kmly")
track.properties.set({ 'hintContent':res.geoObjects.properties.get('description')})
.set({ 'balloonContentHeader':res.geoObjects.properties.get('name')})
.set({ 'balloonContentBody':res.geoObjects.properties.get('description')})
res.geoObjects.each(
function (geoObj) {
if(geoObj.geometry.getType()=="Point") {
geoObj.properties.set({ 'iconContent':geoObj.properties.get('name')})
.set({ 'hintContent':geoObj.properties.get('description')+' км/ч'})
.set({ 'balloonContentHeader':'Время: '+geoObj.properties.get('name')})
.set({ 'balloonContentBody':'скорость: '+geoObj.properties.get('description')+' км/ч'})
geoObj.options.set({'preset':'twirl#nightStretchyIcon'})
var coord = geoObj.geometry.getCoordinates()
var myGeocoder = ymaps.geocode(coord, {kind: 'street'});
myGeocoder.then(
function (resGeo) {
var street = resGeo.geoObjects.get(0);
var name = street.properties.get('name');
geoObj.properties.set({ 'balloonContentFooter':'где: '+name})
});
clusterer.add(geoObj);
track.geometry.insert(track.geometry.getLength(),coord)
}
});
myMap.geoObjects.add(clusterer);
myMap.geoObjects.add(track);
},
function (error){ alert('Ошибка: ' + error); }
);
});
}
</script>
</head>
<body>
<div id="map" style="width:100%; height:100%"></div>
</body>
</html>

Картинка в начале поста показывает что получается. Возможность тонкой настройки стилей отображения всех объектов непосредственно в JavaScript позволяет делать выбор свистелок в зависимости от клиентского терминала. Например, в иконку кнопки вставляется время, в подсказку - скорость, в балун - вся информация из KML внедренная в HTML + обратный геокод (адрес в этой координате). В GM это всё тоже есть, но KML объект ковырять мне лично было лень :).

Service-style

Собственно с чего все началось. Изначально я хотел, чтобы во время поездки с работы домой мои координаты сами сливались на сервер, а там уж кому надо, посмотрит. Так как, катаюсь я не только на авто, но и на велосипеде (тело в сумке, смотреть некогда - того гляди кто-то на таран пойдет или под колесо устремится), смотреть и тыкать в него не досуг.
Самое простое решение - Tasker. Он с указанным интервалом и координату возьмет, и на сервер ее снесет, и ответ сервера обработает, и говорилке на прочтение отдаст.
Task: t tracking (84)
A1: Get Location [ Source:GPS Timeout (Seconds):30 Continue Task Immediately:Off Keep Tracking:Off Continue Task After Error:On ]
A2: Variable Set [ Name:%spd To:round(%LOCSPD*3.6) Do Maths:On Append:Off ]
<send loc>
A3: HTTP Get [ Server:Port:user:pass@my.site.org Path:cgi-bin/script.cgi?%LOC,%spd Attributes: Cookies: Timeout:30 Mime Type:text/plain Output File: ]
<o.k.?>
A4: If [ %HTTPR ~ 200 ]
A5: If [ %HTTPD Is Set ]
A6: Media Control [ Cmd:Pause Simulate Media Button:On ]
A7: Say [ Text:Код: %HTTPR.
Смотрели в %HTTPD Engine:Voice:com.svox.classic:rus-RUS Stream:1 Pitch:5 Speed:5 Continue Task Immediately:On Continue Task After Error:On ]
<echo>
A8: Popup [ Title:Код: %HTTPR Text:Смотрели в %HTTPD Background Image: Layout:PopUp Timeout (Seconds):15 Show Over Keyguard:Off ] If [ %SCREEN ~ on ]
A9: End If
A10: End If


Простой профиль каждые пять минут гоняет довольно простую задачку: фиксим координату и скорость, отправляем на сервер, если сервер помимо ОК вернет еще что-то, покажем это в Pop-Up диалоге и скажем по громкой связи (удобно, если экран выключен). Я так узнаю когда меня смотрели. А где я был в то время, представляю. В МПС что-то изменится, конечно же.
Наверное найдется еще не мало решений автоматического сброса координат серверу, но коль скоро tasker уже крутится, добавим ему оборотов.

Сервер

Тут две части: веб-сервер, как шлюз к функционалу и скрипт, реализующий функционал. Выбор технологий остается за Вами. Тут - основные идеи.

Веб-сервер

Функцию сервера возложим на скучающий NAS, на котором уже крутится Apache. У меня версия сервера 2.2.22. Синтаксис и директивы могут отличаться для других версий.
Имеет смысл создать еще один виртуальный сервер - отделить мух от котлет, чтоб не было неразберихи когда попадем в инфекционное.
<VirtualHost 192.168.xxx.xxx:80>
ServerAdmin admin@server.domain
ServerName tracker

LogLevel emerg
ErrorLog /path/to/server/stuff/error.log
CustomLog /
path/to/server/stuff/access.log "common"
ScriptAlias /cgi-bin/ "/
path/to/server/stuff/cgi-bin/"
DocumentRoot /
path/to/server/stuff/htdocs
AddType text/html .html

В корне искать нечего, обращаться только к конкретным страницам
<Location />
Order Deny,Allow
Deny from all
Allow from 192.168.xxx.0/24
</Location>

Для html страниц, предназначенных для отображения карт ставим тип авторизации Digest,  т.к. это вход в систему и его нужно закрыть получше. Впрочем, это не панацея и вариантов защиты масса. Здесь описан только один из них.

<LocationMatch "/*.html$">
Order Deny,Allow
Deny from all
# Allow from all
Allow from 192.168.xxx.0/24
Satisfy Any
AuthType Digest
AuthName "tracker"
AuthDigestProvider file
AuthUserFile
/path/to/server/stuff/auth/.digest_pw
Можно конечно и Basic.

# AuthType Basic
# AuthName "tracker"
# AuthUserFile
/path/to/server/stuff/auth/.basic_pw
Require valid-user

</LocationMatch>


Со скриптом пришлось повозиться. Tasker умеет авторизоваться пока только по Basic. Трек запрашивают серверные загрузчики Google или Яндекса или встроенный загрузчик в Мобильных Яндекс Картах  через функции API. Однако, авторизоваться умеет только загрузчик Яндекса в API 2.0. Найти описание методов авторизации у Яндекса мне не удалось. Пришлось пользоваться Basic. Ни загрузчик Google, ни загрузчик МЯК (в самодельных виджетах) пока и этого делать не умеют. Поэтому с помощью модуля Rewrite, при запросе KML для Google карт, установим переменную окружения "Google", а при запросе от виджета МЯК - "YaWidget":
<IfModule rewrite_module>
RewriteEngine On
# RewriteLog
/path/to/server/stuff/rewrite.log
# RewriteLogLevel 5
RewriteCond %{QUERY_STRING} =kmlg
RewriteRule ll\.cgi - [E=Google]
RewriteCond %{QUERY_STRING} =ywid
RewriteRule ll\.cgi - [E=YaWidget]
</IfModule>

Теперь при запросе скрипта, данные будут выданы если вызов был: а) из локальной сети или б) с одним из вышеуказанных параметров или в) c авторизацией клиента.
<LocationMatch "/cgi-bin/ll.cgi$">
Order Deny,Allow
Deny from all
Allow from 192.168.xxx.0/24
Allow from env=Google

Allow from env=YaWidget
Satisfy Any
AuthType Basic
AuthName "tracker"
AuthUserFile /
path/to/server/stuff/auth/.basic_pw
Require valid-user
</LocationMatch>
</VirtualHost>


В журнале доступа Apache можно увидеть как это было:
tail /path/to/server/stuff/access.log
<client_IP> - - [27/Dec/2012:16:20:32 +0400] "GET /track-google.html HTTP/1.1" 403 -
<client_IP> - tracker [27/Dec/2012:16:20:32 +0400] "GET /track-google.html HTTP/1.1" 200 4403
173.194.98.22 - - [27/Dec/2012:16:20:37 +0400] "GET /cgi-bin/ll.cgi?kmlg HTTP/1.1" 200 2426
<client_IP> - - [27/Dec/2012:16:29:32 +0400] "GET /track-yandex.html HTTP/1.1" 403 -
<client_IP> - tracker [27/Dec/2012:16:29:32 +0400] "GET /track-yandex.html HTTP/1.1" 200 4403
95.108.144.62 - yandex [27/Dec/2012:16:29:34 +0400] "GET /cgi-bin/ll.cgi?kmly HTTP/1.1" 200 1958

Скрипт

CGI - как много в этом звуке ... путей решения задачи. Исторически сложилось так, что мои экзерсисы пока находятся на этапе упрощенной реализации трекера. Поэтому, особо не мудря, я не стал применять ни PHP, ни Perl, ни тем более C и пр. Самое простое что подвернулось под руку - Shell+Awk. Обоснование: для домашнего применения достаточно авторизации на уровне сервиса, скорость исполнения кода не критична, возможности языка позволяют решать задачи и поинтереснее, отладка скрипта - простым прогоном в консоли, среда исполнения присутствует и настроена по определению.
Скрипт будет один - на все руки от скуки. Все параметры запроса к нему должны умещаться в первом параметре вызова, т.к. никаких GET массивов у нас не будет. У нас, это у меня. Вы делайте как Вам хочется :). То есть, конечно же, в Awk есть ENVIRON["REQUEST_URI"] и иже с ними, но реально это может понадобиться только в МПС.
Итак, начало Awk скрипта устанавливает переменные, указатели на файлы:
#!/bin/sh

if [[ $# -eq 1 ]] ;then
echo $1 | awk '
BEGIN {
FS=","
cmd="date +%k:%M"; cmd|getline time
cmd="date +%F"; cmd|getline date
ftime_check="/path/to/server/stuff/tracks/ll.checked"
ftrack="
/path/to/server/stuff/tracks/ll.track"
ftrack_tpl="
/path/to/server/stuff/xml/track_tpl.xml"
ftrack_point_tpl="
/path/to/server/stuff/xml/track_point_tpl.xml"
ftrack_line_tpl="
/path/to/server/stuff/xml/track_line_tpl.xml"
ftrack_point_y_tpl="
/path/to/server/stuff/xml/track_point_y_tpl.xml"
ftrack_wid_tpl="
/path/to/server/stuff/xml/track_wid_tpl.xml"
ftrack_widpoint_tpl="
/path/to/server/stuff/xml/track_widpoint_tpl.xml"
ftrack_widline_tpl="
/path/to/server/stuff/xml/track_widline_tpl.xml"
ftime_view="
/path/to/server/stuff/tracks/ll.viewed"
ffail="
/path/to/server/stuff/tracks/ll.failed"}

В начале сеанса клиент должен очистить трек на сервере и установить текущую дату в заголовок трека. То есть, если параметр - команда очистки, выполним очистку и вернем OK.

/^tc$/{
# clear track
print date > ftrack
printf "%s\n\n","Content-Type: text/plain"
next
}



Если параметр в формате <Long>,<Lat>,<Speed(км/ч)>, то запишем все в файл чек-пойнта, в файл трека и вернем клиенту OK с довеском информации когда и чем интересовались его местоположением.
/^[0-9]{1,2}\.[0-9]{2,8},[0-9]{1,2}\.[0-9]{2,8},[0-9]{1,3}$/{
# file Check Point
printf "%s", $0" "time > ftime_check
printf "%s\n", $0" "time >> ftrack
# output to Tasker
getline time_view < ftime_view
printf "%s\n\n","Content-Type: text/plain"
if (time_view ~ /.+/) print time_view
# clear view timestamp
print "" > ftime_view
next
}


Далее идут команды от клиентов просмотрщиков. В каждой их них есть запись времени и метода просмотра в файл, который выдается отмечающемуся (см. выше):
# when and by what they look for ...
printf "%s","Мобильных Яндекс картах в "time > ftime_view



Ответ для мобильных Google Maps.

Крайняя точка (простое перенаправление):
/^gm$/{
# when and by what they look for ...
printf "%s","Гугл картах в "time > ftime_view
FS=" "
getline < ftime_check
printf "%s\n\n","Location: geo:0,0?q="$1" (V.Oz. at "$2")"
next
}

Трек целиком (перенаправление на получение KML. См. ниже):

/^gmt$/{
# when and by what they look for ...
printf "%s","Гугл картах в "time > ftime_view
FS=" "
getline < ftime_check
  printf "%s\n\n","Location: geo:0,0?q=http://my.site.org:8080/cgi-bin/script.cgi?kmlg"
next
}


Ответ для мобильных Яндекс Карт.
Крайняя точка (простое перенаправление):
/^ym$/{
# when and by what they look for ...
printf "%s","Мобильных Яндекс картах в "time > ftime_view
FS=" "
getline < ftime_check
printf "%s\n\n","Location: geo:"$1"?z=13&q=(V.Oz. at "$2")"
next
}

Трек целиком через виджет:

/^ywid$/{
# when and by what they look for ...
printf "%s","МЯК виджете в "time > ftime_view
# output to YaMaps widget
FS=" "
getline track_date < ftrack
getline widpoint_tpl < ftrack_widpoint_tpl
getline widline_tpl < ftrack_widline_tpl
while (getline < ftrack){
tmp = widpoint_tpl
split($1,point,",")
sub(/%%STYLE%%/,"voz",tmp)
gsub(/%%TIME%%/,$2,tmp)
sub(/%%SPEED%%/,point[3],tmp)
sub(/%%LATITUDE%%/,point[2],tmp)
sub(/%%LONGITUDE%%/,point[1],tmp)
points = points""tmp
line = line""point[2]" "point[1]"\n"
}
sub(/%%LINE%%/, line, widline_tpl)
sub(/%%STYLE_LINE%%/, "vozLine", widline_tpl)
sub(/%%DATE%%/, track_date, widline_tpl)
points = points""widline_tpl
printf "%s\n\n","Content-Type: text/xml"
while (getline < ftrack_wid_tpl){
sub(/%%POINTS%%/,points)
print
}
next
}

Для виджета сервер отдает заполненный шаблон YMapsML:
<ymaps xmlns="http://maps.yandex.ru/ymaps/1.x" xmlns:gml="http://www.opengis.net/gml" xmlns:repr="http://maps.yandex.ru/representation/1.x" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maps.yandex.ru/schemas/ymaps/1.x/ymaps.xsd">
<repr:Representation>
<repr:View>
<repr:mapType>MAP</repr:mapType>
<gml:boundedBy>
<gml:Envelope>
<gml:lowerCorner>0 0</gml:lowerCorner>
<gml:upperCorner>0 0</gml:upperCorner>
</gml:Envelope>
</gml:boundedBy>
</repr:View>
</repr:Representation>
<GeoObjectCollection>
<gml:featureMembers>
%%POINTS%%
</gml:featureMembers>
</GeoObjectCollection>
</ymaps>

Где поле %%POINTS%% заполняется шаблонами точек и линии:
<GeoObject><gml:style>#%%STYLE%%</gml:style><gml:description><![CDATA[%%TIME%% скорость: %%SPEED%% км/ч]]></gml:description><gml:Point><gml:pos>%%LATITUDE%% %%LONGITUDE%%</gml:pos></gml:Point></GeoObject>

<GeoObject><gml:style>#%%STYLE_LINE%%</gml:style><gml:description><![CDATA[%%DATE%% line]]></gml:description><gml:LineString><gml:posList>%%LINE%%</gml:posList></gml:LineString></GeoObject>



Т.к. API и Google карт и Яндекс карт умеют обрабатывать данные KML контейнеров, сделаем для них единый обработчик трека. Учтем только, что долгота и широта у них идут в различном порядке. Для простоты реализации, скрипт будет обрабатывать шаблоны контейнера и точки. Шаблоны точки должны быть на одной строке.
xml/track_tpl.xml
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://earth.google.com/kml/2.1">
<Document>
<name>V.Oz. on Track</name>
<description>трек от %%DATE%%</description>
%%POINTS%%
</Document>
</kml>


xml/track_point_tpl.xml
<Placemark><name>Время %%TIME%%</name><description>Скорость %%SPEED%% км/ч</description><Point><coordinates>%%LATITUDE%%,%%LONGITUDE%%</coordinates></Point></Placemark>

more xml/track_point_y_tpl.xml
<Placemark><name>%%TIME%%</name><description>%%SPEED%%</description><Point><coordinates>%%LATITUDE%%,%%LONGITUDE%%</coordinates></Point></Placemark>


Собственно команда:
/^kml.$/{
# output to Maps
FS=" "

Скрипт читает дату трека
getline track_date < ftrack
if(/kmly/){

Если контейнер идет Яндексу, берем соответствующий шаблон точки
printf "%s","Яндэкс картах на компе в "time > ftime_view
getline point_tpl < ftrack_point_y_tpl
}else{

Иначе берем шаблон для Google Maps
printf "%s","Гугло картах на компе в "time > ftime_view
getline point_tpl < ftrack_point_tpl
getline line_tpl < ftrack_line_tpl
}

Далее берем данные трека и замещаем ими поля шаблона точки. Создаем строку с точками и строку с линей.
while (getline < ftrack){
if(/.+/){
tmp = point_tpl
split($1,point,",")
sub(/%%TIME%%/,$2,tmp)
sub(/%%SPEED%%/,point[3],tmp)
sub(/%%LATITUDE%%/,point[2],tmp)
sub(/%%LONGITUDE%%/,point[1],tmp)
points = points""tmp
line = line""point[2]","point[1]",0\n"
}
}
sub(/%%LINE%%/, line, line_tpl)
sub(/%%DATE%%/, track_date, line_tpl)
points = points""line_tpl

Подставив точки и дату в поля шаблона контейнера, выдаем его загрузчику
printf "%s\n\n","Content-Type: text/xml"
while (getline < ftrack_tpl){
sub(/%%DATE%%/,track_date)
sub(/%%POINTS%%/,points)
print
}

next
}



Если же придет левая команда, запишем ее в журнал. Конец скрипта.
{
cmd="date"; cmd|getline date
print date" ARGS="$0 >> ffail
}
'
else exit 1;
fi

Конечно же команд может быть и больше. Принцип работы точно такой же.
Данный скрипт работает пока для одного отслеживаемого клиента. Для реализации многопользовательской системы, нужно взять данные авторизации сервера и расширить функционал скрипта, обработав его напильником.

Вместо эпилога

Если меня кто-то спросит "А стоило ли городить огород?", отвечу "Несомненно ДА! Хотя бы ради разминки для мозгов. Ведь им всегда нужно подсовывать принципиально новые задачки." А если это несет какую-то практическую нагрузку, то тем лучше.
Все навыки, которые требуются для создания такой системы постепенно собирались мною при решении разного рода пользовательских задачек. Ничего сверхъестественного тут нет и описания, применяемого инструментария, есть в Сети. Встречающиеся на пути засады также подробно разобраны и решены пытливыми юзерами.
На базе описанного решения можно запросто смастерить что-то совсем другое. И если мое описание покажет Вам как можно воплотить Вашу давно сверлящую мозг задумку, буду рад.

"Покупайте наших слонов!" (с)