SASGIS

Веб-картография и навигация

Знакомство со скриптами в SAS.Planet (Garmin Quick Draw)

программа для загрузки и просмотра спутниковых снимков Земли, Луны, Марса предоставленных сервисами Google Maps и Космоснимки. Возможность работы с GPS приёмником.

Модератор: Tolik

Знакомство со скриптами в SAS.Planet (Garmin Quick Draw)

Сообщение VadimK60 » 07 мар 2021, 02:28

Как запретить скачивание области, покрытой пустым тайлом ?

Здравствуйте!
Возник вопрос, ответ на который мне найти пока не удалось.
По крайней мере в описании формата ZMP и других разделах сайта я ответа не нашёл. :(

Создал свой ZMP. В папке EmptyTiles лежит образец пустого тайла. Всё работает, пустые тайлы на диск не сохраняются.

Но проблема в том, что в области, покрываемой пустым тайлом, на ВСЕХ последующих зумах нет полезной информации -- ВСЕ тайлы пустые.

Вопрос: как донести до программы, чтобы она даже не пыталась на последующих зумах скачивать области, покрытые пустыми тайлами ?

Иначе пока получается пустое расходование времени и ресурсов. Процентов 99% возвращаемых тайлов пустые!

Есть ли в программе подобный функционал ? Или придётся через скрипт всё организовывать ? (очень не хотелось бы)
Последний раз редактировалось VadimK60 11 мар 2021, 14:50, всего редактировалось 2 раз(а).

За это сообщение автора VadimK60 поблагодарил:
garl (13 мар 2021, 09:20)
Рейтинг: 5.26%
 
VadimK60
Новичок
 
Сообщения: 25
Зарегистрирован: 24 окт 2018, 23:18
Благодарил (а): 6 раз.
Поблагодарили: 4 раз.

Re: Как запретить скачивание области, покрытой пустым тайлом ?

Сообщение VadimK60 » 10 мар 2021, 15:24

По результатам анализа ответов :) на поставленный в первом сообщении вопрос сразу перешёл к написанию скрипта.

Работа со скриптами порадовала. По крайней мере, мне удалось реализовать свою хотелку.
Изучал по статье. Ещё немного полезной информации есть в описании ZMP.

По ходу работы выяснились следующие ограничения:

  • Отсутствует работа с файловыми типами. Есть возможность записи буфера в файл, но отсутствует возможность чтения файла, добавления (append) к файлу.
    .
  • Почему-то в скриптах отсутствует доступ к параметру NameInCache файла param.txt. Путь до папки в кэше приходится вбивать в скрипт ручками.
    .
  • Если тайл сохранять не нужно, то его адрес не указывается: ResultURL:='';
    Если непосредственно из скрипта нужно скачать что-либо, то в param.txt не забываем добавить IsUseDownloaderInScript=1

    НО если мы посредством скрипта скачали нужный тайл и сохранили его в нужное место в кэше, и при этом желаем, чтобы программа этот тайл повторно не скачивала (ResultURL:=''), то в режиме просмотра карты мы этот тайл рискуем не увидеть.
    Пока программа сама этот тайл не скачала по ссылке в ResultURL, она считает, что его нет.
    Точнее, прога сможем увидеть скачаный скриптом тайл, но только при смене зума, когда он подгрузится уже из кэша.
    Вот такая интересная особенность...

    Поэтому, если предварительно необходимо скачать тайл в скрипте, чтобы в процессе выяснить нужную информацию о нём,
    то приходится затем передавать программе ссылку на этот тайл. Получается, что файл скачивается дважды. А это двойная нагрузка на сервер...
    Правда, если тайл сразу отображать не надо, то можно скачивать его скриптом, а ссылку на него для программы обнулять. Это полезно в случае закачки области. Но тогда как скрипту узнать, кто его вызывает: просмотрщик карты или качалка ?
    .
  • Скрипт отрабатывает (запускается, работает, прекращает работу) только непосредственно перед скачиванием очередного тайла.
    Существует возможность передачи информации от одного запуска скрипта к другому - через глобальную переменную (назовём её так) ScriptBuffer.
    К сожалению, единственную глобальную переменную. :(
    Но даже с ней не всё гладко.
    Для ускорения процесса скачивания программа по-умолчанию запускает сразу несколько экземпляров скрипта.
    И, похоже, с доступом к единственной глобальной переменной ScriptBuffer возникают проблемы.
    Если 2 экземпрляра скрипта одновременно начали работу, то есть подозрение, что сохранится только данные, внесённые в переменную скриптом, закончившим свою работу позже.
    Возможно, у каждого потока своя переменная ScriptBuffer. С этим вопросом требуется ещё разобраться.

    А пока из-за столь непредсказуемого (точнее, непонятного мне) поведения пришлось отключить закачку в несколько потоков (MaxConnectToServerCount=1 в params.txt).
    .
  • С отладкой тоже все не так гладко, как хотелось бы.
    Её практически нет.
    В окне резутьтата (Help > Pascal Script IDE в окне Debug Output) есть информация только по пяти переменным (ResultURL,RequestHead,PostData,ScriptBuffer,Version). Пользовательских среди них нет.
    Можно было бы ещё помечтать хотя бы об отладочной консольке, куда можно было бы информацию из скрипта выводить writeln'ом. Но её тоже нет.

    Приходится вместо этого в нужных точках скрипта вставлять запись файлов отладки, содержащих в названии/теле значение интересующих переменных.
    .
  • Условная компиляция ($DEFINE,$IFDEF,$ENDIF...), похоже, не поддерживается.

Такие вот впечатления от первого знакомства со скриптами в SAS.planet.

За это сообщение автора VadimK60 поблагодарил:
SergeyKa (10 мар 2021, 21:03)
Рейтинг: 5.26%
 
VadimK60
Новичок
 
Сообщения: 25
Зарегистрирован: 24 окт 2018, 23:18
Благодарил (а): 6 раз.
Поблагодарили: 4 раз.

Re: Первое знакомство со скриптами в SAS.Planet

Сообщение VadimK60 » 11 мар 2021, 11:09

Продолжаю свой "микроблог". :)

Немного причесал свой скрипт и готов представить его публике.
Цель: общедоступные карты глубин Garmin QuickDraw
( https://connect.garmin.com/modern/quick ... fullScreen )

Простейший скрипт GetUrlScript.txt для него состоит всего из нескольких строк:
Код: Выделить всё
Var G,mask:integer;
    i:byte;
    GRMN:string;
Begin
  GRMN:='';
  for i:=1 to GetZ-1 do
  begin
    G:=0;
    mask:=1 shl (i-1);
    if ((GetX and mask)<>0) then G:=G+1;
    if ((GetY and mask)<>0) then G:=G+2;
    GRMN:=intToStr(G)+GRMN;
  end;
  ResultURL:=GetUrlBase+GRMN+'.png?units=m';
End.


Но беда в том, что 90% скачиваемых тайлов будут пустыми. Ну нет на нашей территории такого количества рек и озёр, а те , что есть, отрисованы не полностью. Даже если пользоваться полигональным выделением, процент всё же будет достаточно большим.
Т.е. необходима доработка.

Была замечена интересная особенность гарминовской системы: если на каком то уровне попадается пустой тайл, то на площади, покрываемой этим тайлом, на всех последующих уровнях тайлы будут пустыми и скачивать их нет смысла !!!

Например, если на уровне 10 присутствует пустой тайл, то на уровне 11 под этим тайлом будут находиться 4 пустых тайла, на уровне 12 - уже 16 пустых тайлов, на 13 -- 64 тайла и т.д.. И ведь скачивать их нет никакого смысла! Это будет просто пустая нагрузка на сервер и значительная потеря времени. Если на второе можно и наплевать, то излишняя нагрузка на сервер впоследствии может обернуться серьёзным усложнением (со стороны хостера) доступа.

Тут ещё один момент возникает. Если мы в вышеприведённом примере начинаем скачивание с 13 зума и натыкаемся на пустой тайл, то имеет смысл проверить на пустоту все тайлы "над" этим тайлом. В нашем случае все тайлы до 11 уровня пустые. И сделав 3 контрольных скачивания до первого непустого зума (тайлы 12, 11 и 10 уровней) мы понимаем, что скачивать остальные 63 пустых тайла на 13-м зуме смысла нет!

Собственно, этот подход и был реализован в доработанном скрипте, который я и предоставляю вашему вниманию:

Код: Выделить всё
Const DefUrlBase ='https://downloadqdc.garmin.com/GCSProxyServlet/MarineImages/';
      NameInCache='С:cache\GarminQD'; //необходимо изменить букву диска под свои условия
      DLoaderMode  =false;
      SaveScriptBuf=false;
//---------------------------------------------------------
// получение ссылки на тайл

Function GetGrmnCoord:string;
var G,mask:integer;
    i:byte;
    GRMN:string;
Begin
  GRMN:='';
  for i:=1 to GetZ-1 do
  begin
    G:=0;
    mask:=1 shl (i-1);
    if ((GetX and mask)<>0) then G:=G+1;
    if ((GetY and mask)<>0) then G:=G+2;
    GRMN:=intToStr(G)+GRMN;
  end;
  Result:=GRMN;
//ResultURL:=DefUrlBase+GRMN+'.png?units=m';
End;

//---------------------------------------------------------
//проверка вышележащих тайлов на пустоту; если скачивание идёт с 1-го зума, то эта проверка не особо и нужна

Function FindUpperEmptyTile(Coord:string):string;
Var   VResponseCode                 : Cardinal  ;
      VResponseHeader, VResponseData: AnsiString;
      VRequestUrl, VRequestHeader   : AnsiString;
      NewCoord,LastEmpty:string;
      i:byte;
Begin
  Result   :=Coord;
  NewCoord :=Coord;
  LastEmpty:='';

  if Coord<>'' then
  if Assigned(Downloader) then
  begin
    for i:=length(Coord) downto 3 do
    begin
       LastEmpty:=NewCoord;
       delete(NewCoord,length(NewCoord),1);

       VRequestUrl     := DefUrlBase+NewCoord+'.png?units=m';
       VRequestHeader  := '';
       VResponseHeader := '';
       VResponseData   := '';
       VResponseCode   := Downloader.DoHttpRequest(VRequestUrl, VRequestHeader, '', VResponseHeader, VResponseData);

       if VResponseCode = 200 then
       begin
         if ((Pos('Content-Length: 749'+#13,VResponseHeader)=0)
         and (Pos('Content-Length: 355'+#13,VResponseHeader)=0))  then // размер пустых тайлов =749 и =355 байт
         begin
           Result:=LastEmpty;
           break;
         end;
       end
       else
       begin
         Result:=LastEmpty;
         break;
       end;
    end;
  end;
End;
//---------------------------------------------------------
VAR
  VResponseCode                 : Cardinal  ;
  VResponseHeader, VResponseData: AnsiString;
  VRequestUrl, VRequestHeader   : AnsiString;

  i           :byte;
  isEmpty     :boolean;
  GrmnCoord,
  tmpGrmnCoord:string;
  GrmnFullFName:string;
//---------------------------------------------------------

BEGIN
  ResultURL:='';

// Переменная ScriptBuffer служит для хранения и переноса списка пустых тайлов.
// Тайлы хранятся в гарминоском формате.
  if ScriptBuffer='' then ScriptBuffer:=';';
  if ScriptBuffer[length(ScriptBuffer)]<>';' then ScriptBuffer:=ScriptBuffer+';';

  GrmnCoord:=GetGrmnCoord;

  isEmpty:=false;
  tmpGrmnCoord:=GrmnCoord;
  for i:=length(GrmnCoord) downto 3 do
  begin
    if Pos(';'+tmpGrmnCoord+';',ScriptBuffer)<>0 then
    begin
      isEmpty:=true;
      break;
    end;
    delete(tmpGrmnCoord,length(tmpGrmnCoord),1);
  end;

  if not isEmpty then
  if Assigned(Downloader) then
  begin
     VRequestUrl     := DefUrlBase+GrmnCoord+'.png?units=m';
     VRequestHeader  := '';
     VResponseHeader := '';
     VResponseData   := '';
     VResponseCode   := Downloader.DoHttpRequest(VRequestUrl, VRequestHeader, '', VResponseHeader, VResponseData);

     if VResponseCode = 200 then
     begin
      if ((Pos('Content-Length: 749'+#13,VResponseHeader)<>0)
       or (Pos('Content-Length: 355'+#13,VResponseHeader)<>0)) then
      begin
        ScriptBuffer:=ScriptBuffer+FindUpperEmptyTile(GrmnCoord)+';'; //все пустые тайлы с наименьшим Z записываются в ScriptBuffer
//      ScriptBuffer:=ScriptBuffer+                  (GrmnCoord)+';';                     //DEBUG
         if SaveScriptBuf then SaveToLocalFile(NameInCache+'\ScriptBuffer', ScriptBuffer); //DEBUG
        ResultURL:='';
      end
      else
      begin
//        SaveToLocalFile(NameInCache+'\GRMN\'+GrmnCoord+'.png', VResponseData); //DEBUG

        if DLoaderMode then //режим качалки или просмотрщика ? у второго нагрузка на сервер в 2 раза больше, но он более универсален
        begin
          GrmnFullFName:='\z'+IntToStr(GetZ)+'\'+IntToStr(GetX div 1024)+'\x'+IntToStr(GetX)+'\'+IntToStr(GetY div 1024)+'\y'+IntToStr(GetY)+'.png';  //CacheType=2
          SaveToLocalFile(NameInCache+GrmnFullFName, VResponseData);
          ResultURL:='';
        end
        else ResultURL:=DefUrlBase+GrmnCoord+'.png?units=m'; //viewer mode with double tile download
      end;
     end;
  end;
END.

Скрипт работает, но и его можно и нужно усовершенствовать. Недостатки я описывал в предыдущем сообщении.
Например, в нём нужно руками исправить путь до папки с кэшем в в переменной NameInCache.

Итоговый файл PARAMS.TXT
Код: Выделить всё
[PARAMS]
GUID={CBA03063-23D9-FAAF-CDDC-9182B98644B1}
asLayer=1
name_ru=GarminQD
name   =GarminQD
name_uk=GarminQD
CacheType=2
DefURLBase=
projection=1
sradiusa=6378137
sradiusb=6378137
NameInCache=GarminQD
Ext=.png
ContentType=image/png
UseDwn=1
IsUseDownloaderInScript=1
MaxConnectToServerCount=1


По поводу многопоточности (MaxConnectToServerCount). Можно выставить значение отличное от 1 (по умолчанию =4). Как мне объяснили в ЛС, для каждого потока будет создан свой экземпляр ScriptBuffer. Так что ничего особо страшного произойти не должно, кроме того, что каждый поток будет самостоятельно составлять список пустых тайлов.

PS: 2021-03-13 Перезалил архив с ZMP: в скрипте исправил баг, внесённый случайно непосредственно перед первой публикацией; из params.txt убрал ограничение на количество потоков.
PPS: 2021-03-20 Перезалил: Добавил приведение ResponseHeader к нижнему регистру.
Вложения
GarminQD.zmp.zip
ZMP для Garmin Quick Draw
(4.54 KiB) Скачиваний: 460

За это сообщение автора VadimK60 поблагодарили: 2
monop (20 окт 2022, 21:28) • NStager (28 окт 2021, 00:04)
Рейтинг: 10.53%
 
VadimK60
Новичок
 
Сообщения: 25
Зарегистрирован: 24 окт 2018, 23:18
Благодарил (а): 6 раз.
Поблагодарили: 4 раз.


Вернуться в SAS.Планета

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 95