Przez ostatnie trzy tygodnie mam przyjemność bawić się SDK dla Silverlight i WPF dostępu do serwerów map firmy ESRI. Po tych tygodniach mogę powiedzieć na pewno, że forum ArcGIS jest jednym z najgorszych, na jakie natrafiłem w ostatnich latach. Nie tylko pod względem zawartości (która jest rozbita pomiędzy starą wersją, jak i nową) czy też funkcjonalności wyszukiwania, ale przede wszystkim pod względem odpowiedzi lub braku odpowiedzi.
Nie zależnie gdzie się pójdzie czy to będą devpytania, StackOverflow, MSDN Forums czy też Telerik Forums na odpowiedź długo czekać nie trzeba – nawet jeżeli będzie ona typu: RTFM, ale będzie. Ja na forum ArcGIS czekam średnio dwa tygodnie na jakąkolwiek odpowiedź a jak już ktoś odpowiada – nawet pracownik ESRI, to odpowiedź brzmi „u mnie działa, masz coś źle”. Sorki ale to nie jest miejsce dla osób poszukujących wiedzę. Zaś dokumentacja API jest szokująco podobna do Ctrl+Shift+D w GhostDoc:

albo lepiej, dokładniejsza dokumentacja jest dla typów internal zamkniętych w bibliotece – dowód?

Ja tego po prostu nie rozumiem :) A w tym „useful” to się po prostu zakochałem :)
Wracajmy jednak do tematu. Przy wykorzystaniu w naszej aplikacji warstwy prezentacji ArcGISDynamicMapServiceLayer (służącej do prezentowania dynamicznie ładowanych obrazków z serwera - bez ich wcześniejszego z cachowania - za pomocą usługi REST), mamy możliwość określenia widocznych warstw, które mają być zwrócone po odpytaniu usługi REST. Wszystkie te widoczne warstwy przypisujemy do tablicy int VisibleLayers (wartością jest ID warstwy). Zaraz po przypisaniu, generowany jest odpowiedni request, który może trwać do kilku sekund i zwracany nam jest obrazek, który może być wielkości 100KB jak nie większej (lub mniejszej). Takie odpytanie jest robione za każdym razem, kiedy zmienimy zawartość VisibleLayers czy też kiedy zmienimy obszar widoczności mapy (zoom in/out, przesunięcie w bok itp.), działa to mniej więcej na tej samej zasadzie co Google Maps czy Bing Maps – obrazki które nie są wyświetlane w danym momencie nie są ładowane.
By mieć pewność, iż obrazek wyświetlany jest zaktualizowany (uległ zmianie od ostatniego ściągnięcia), ArgGISDynamicMapServiceLayer udostępnia nam możliwość określenia czy odpowiedź serwera ma nie być cachowana w naszej przeglądarce - DisableClientCaching. Domyślnie cachowanie jest włączone. Dzięki czemu jeżeli będziemy ładować te same dane dla VisibleLayers w takim samym obszarze mapy, to obrazek zostanie zwrócony z serwera tylko raz a następnie będzie on wykorzystywany ponownie. Przynajmniej takiej jest założenie.
Niestety nie działa to tak jak powinno – przynajmniej moim zdaniem, ale o tym później. Okazuje się, iż kolejność przekazywanych IDków warstw ma kolosalne znaczenie. Dla przykładu (przy założeniu, iż cachowanie nie jest wyłączone, oraz nie robimy zoom in/out i nie zmieniamy obszaru wyświetlania danych):
- Jeżeli przekażemy ID {1,2} do VisibleLayers, zostanie utworzony request i obrazek zostanie nam zwrócony;
- Jeżeli teraz wyzerujemy VisibleLayers {}, obrazek nam zniknie, request nie zostanie wywołany gdyż „background” mamy już załadowany;
- Jeżeli znów przekażemy ID {1,2} do VisibleLayers, zostanie nam zwrócony obrazek z cachu przeglądarki;
- Jeżeli teraz przekażemy ID {2,1} do VisibleLayers, zostanie utworzony request i obrazek zostanie nam zwrócony;
- Reszta działa tak jak w punkcie 2 i 3.
Ok, takie działanie miałoby ręce i nogi gdyby obrazek zwracany różnił się i gdyby kolejność ID miała znaczenie, niestety przy serwerze REST kolejność ID nie ma znaczenia gdyż zdefiniowana ona jest w pliku MDX na serwerze. Więc jeżeli chcemy zwrócić warstwy {1,2} zaś MDX ma kolejność {2,1} to zostanie nam zwrócony obrazek zgodnie z kolejnością MDX czyli {2,1}.
Co takie zachowanie powoduje? Jeżeli damy użytkownikowi końcowemu możliwość wybierania widocznych warstw na mapie, to dla zbioru n warstw (przy założeniu, że zawsze na tym samym zbiorze operujemy i zawsze pokazujemy n warstw) możemy wygenerować n! (silnia) requestów, które ściągną nam ten sam obrazek n! razy. Teraz wyobraźcie sobie, iż operujecie na 4 warstwach, obrazek zwracany jest wielkości 100KB. 4 warstwy to 24 kombinacje, które użytkownik może sobie wybrać, dające łącznie ~2,5MB ściągniętych danych tego samego obrazka, nie różniącego się niczym od tego jak go raz nam się udało pobrać.
Dzieje się tak gdyż w bebechach SDK istnieje sobie taki fragment kodu:

Który nie zwraca uwagi na kolejność przekazanych ID. Przez co request idący do serwera różni się tylko i wyłącznie tym fragmentem zamiast 1,2 w stringu jest 2,1 co oznacza inne zapytanie (i słusznie przeglądarki tak to rozumieją), zaś dla serwera ArcGIS nie ma to żadnego znaczenia.
Rozwiązanie problemu jest dość proste, wystarczy posortować tablicę przed przypisaniem jej do VisibleLayers, dzięki czemu zachowany zawsze taką samą kolejność i zamienimy n! requestów na 1 request.
Teraz wracając do „nie działa to jak powinno – przynajmniej moim zdaniem”. Napisałem na forum ArcGIS o tym i dostałem odpowiedź, że jest to szczególny przypadek, który powinien obsłużyć programista. Ja się z tym nie zgadzam, z kilku powodów:
- Programista o tym nie wie – ja się dowiedziałem przypadkiem, kiedy bawiłem się FireBugiem, dokumentacja zaś na ten temat całkowicie milczy;
- Kontrolka umożliwia wyłączenie cachowanie zwracanych wyników przez przeglądarkę – czyli pozostałe wyniki powinny być cachowane;
- Kolejność VisibleLayers nie ma znaczenia dla serwera ze względu na plik MDX;
- Istnieje taka dokumentacja do klasy internal którą wkleiłem wyżej mówiąca „The throttle timer is useful for limiting the number of requests” – w typ wypadku Throttle jest wykorzystywany do ograniczenia odpytywania REST przy zoom in/out, niestety działa to jak działa ale nie o tym jest ten post, to co jest ważne to to, iż goście od ESRI chcą ograniczyć liczbę requestów… ale chyba tylko w tedy kiedy jest im wygodnie.
Jak dla mnie jest to funkcjonalność, którą powinna dostarczyć kontrolka a nie programista. A jakie jest wasze zdanie? Powinno to być pozostawione programistom z odpowiednią adnotacją w dokumentacji, czy skoro nie ma znaczenia kolejność przekazywanych warstw to powinna to obsłużyć kontrolka?