Access

 MS Access i OpenLayers - znaczniki (markery) na mapie i okienko Popup tzw. dymek.

Odczyt współrzędnych pod kursorem myszy.

Minęło trochę czasu od ostatniej aktualizacji strony, która miała miejsce w styczniu 2019 roku. Ostatnio na grupie dyskusyjnej pl.comp.bazy-danych.msaccess. pojawił się wątek: Pobieranie danych GPS z mapy i wpis do bazy, w którym udzieliłem, a w zasadzie przedstawiłem, tymczasowy przykład odczytu współrzędnych Lon Lat wybranego punktu na mapie. Uznałem, że tymczasowy przykład można trochę rozbudować. Przede wszystkim w wybranym miejscu powinien zostać wstawiony znacznik (marker), który po kliknięciu wyświetli w okienku 'Popup' dane opisowe oraz fotografię dotyczące punktu na który wskazuje znacznik.

Zdarzenia generowane przez znacznik (marker).

O zdarzeniach generowanych przez znacznik (marker) ich rejestrowaniu i obsłudze zdarzeń pisałem na stronie:
Znaczniki - okienko popu. Poniżej, tylko tytułem przypomnienia zamieszczam informacje o obsłużonych zdarzeniach markera.

• mouseover
gdy kursor myszy znajduje się nad zdefiniowanym prostokątnym obszarem markerem, następuje zmiana wyglądu kursora myszy na 'pointer'   Kursor myszy Pointer
• click
gdy kursor myszy znajduje się nad markerem i równocześnie zostaje wciśnięty i puszczony prawy przycisk myszy. Najważniejsze zdarzenie znacznika (markera). Po kliknięciu pojawia się okno typu 'PopUp' zawierające informacje o miejscu wskazywanym przez znacznik.

Tyle tytułem przypomnienia, ale wstawianie znacznika (markera) będzie miało miejsce dopiero po odczytaniu współrzędnych Lon Lat wybranego punktu.

Współrzędne Lon Lat wybranego punktu.

Można przewrotnie powiedzieć nic prostszego. Należy tylko w w funkcji init() zarejestrować i obsłużyć zdarzenie 'clik' zachodzące nad obszarem wyświetlanej mapy. W zdarzeniu tym pobierzemy współrzędne Lon Lat wybranego punktu. Funkcja init() znajduje się w pliku mylInit.js.

...
	// rejestracja zdarzenia kliknięcia na mapie, odczyt (współrzędnych wybranego punktu)
	map.events.register("click", map, function(e) {
		// pobierz współrzędne wybranego punktu
		var position = map.getLonLatFromViewPortPx(e.xy).transform(epsg900913, epsg4326);
		alert('Lon: '+position.lon+'\nLat: '+ position.lat)
	});
...

Po kliknięciu na mapie MS Access, a raczej osadzona strona WWW, wyświetli nam tak oczekiwane okno komunikatu z informacją o współrzędnych wybranego punktu .

Współrzędne punktu   
1. Współrzędne wybranego punktu.

Tyle tylko, że treść komunikatu możemy sobie odczytać wizualnie, ale za pomocą VBA nie można bezpośrednio pobrać wartość zmiennej, lub wartości zwracanej przez funkcję JavaScript.
By odczytać współrzędne punktu wybranego na mapie wyświetlanej w formularzu, musimy skorzystać z JavaScipt i elementu(ów) pola danych 'input', umożliwiający wprowadzanie danych przez użytkownika.
W tym celu dodamy na naszej stronie dwa ukryte pola typu 'input' o identyfikatorach 'lonClick' oraz 'latClick'.

...
</head><body>
	<div id="container">
		<!-- kontener na mapę -->
		<div id="basicMap"></div>
	</div>
	<div id="content">
		<!-- współrzędne wybranego punktu na mapie -->
		<div id="coordinates">
			<input id="lonClick" type="hidden" value="">
			<input id="latClick" type="hidden" value="">
		</div>
	</div>
...
</body></html>

Teraz musimy zmodyfikować funkcję rejestrującą zdarzenie "click" nad obszarem map. Zamiast wyświetlać okno komunikatu, zapiszemy współrzędne Lon Lat w  polach typu 'input'.

...
	// rejestracja zdarzenia kliknięcia na mapie
	map.events.register("click", map, function(e) {
		// pobierz współrzędne wybranego punktu
		var position = map.getLonLatFromViewPortPx(e.xy).transform(epsg900913, epsg4326);
		// zapisz współrzędne w polach danych
		OpenLayers.Util.getElement("latClick").value = position.lat;
		OpenLayers.Util.getElement("lonClick").value = position.lon;
	});
...

Zdarzenie formantu Microsoft Web Browser zawierającego mapę.

Teoretycznie, już wszystko jest proste. W zdarzeniu 'click' zachodzącym nad mapą powinniśmy odczytać współrzędne Lon Lat, z ukrytych pól danych typu 'input'

Niestety, formant Microsoft Web Browser nie oferuje obsługi zdarzenia 'click'. Jedyne zdarzenia, które są obsługiwane przez ten formant, widoczne  są w oknie 'Arkusza Właściwości'.

Właściwości formantu oMsWebBrowser   
2. Zdarzenia formantu Microsoft Web Browser

i są to poniższe zdarzenia:

  • Private Sub oMsWebBrowser_Updated(Code As Integer)
  • Private Sub oMsWebBrowser_Enter()
  • Private Sub oMsWebBrowser_Exit(Cancel As Integer)
  • Private Sub oMsWebBrowser_GotFocus()
  • Private Sub oMsWebBrowser_LostFocus()

w żadnym z tych zdarzeń nie jesteśmy precyzyjnie pobrać współrzędnych Lon Lat klikniętego punktu.

Wczesne wiązanie biblioteki Microsoft HTML Object Library

By rozwiązać ten problem musimy utworzyć odwołanie do biblioteki obiektów poprzez wybranie polecenia „Tools/References” w edytorze Visual Basic. W otwartym oknie dialogowym „References” należy znaleźć bibliotekę Microsoft HTML Object Library i zaznaczyć pole wyboru.

Odwołanie do HTML Object Library   
3. Odwołanie do biblioteki Microsoft HTML Object Library

Mając dodane odwołanie do biblioteki Microsoft HTML Object Library, możemy zadeklarować zmienną obiektową oDivMap jako typ HTMLDivElement za pomocą słowa kluczowego WithEvents. Zmienna tak zadeklarowana udostępni nam dostęp do zdarzeń generowanych przez element <div> w którym osadzona jest nasza mapa. Pozostałe elementy możemy zadeklarować jako obiekt, lub jako ściśle określony typ.

WithEvents - deklarowanie zmiennej udostępniającej zdarzenia

Słowo kluczowe WithEvents określaja, że zmienna deklarowana w ten sposób jest zmienną obiektową udostępniającą zdarzenia wyzwalane przez obiekt ActiveX. Zmienne deklarowane z użyciem słowa kluczowego WithEvents należy zadeklarować jako zmienne obiektowe, akceptujące wystąpienia klas. Nie można za pomocą słowa kluczowego WithEvents zadeklarować zmiennej jako typ Object. Zmienną należy zadeklarować jako konkretną klasę, która może zgłaszać zdarzenia.

...
' div zawierający mapę udostępniający zdarzenia
Private WithEvents oDivMap  As HTMLDivElement
' dokument HTML
Private oDocument           As HTMLDocument     'Object
' ukryte pola typu Input
Private oInputLon           As HTMLInputElement 'Object
Private oInputLat           As HTMLInputElement 'Object
...
'------------------------------------------------------------------------------------
Private Sub Form_Open(Cancel As Integer)
	' załaduj dokument html do formantu oMsWebBrowser
	Call Me.oMsWebBrowser.Object.Navigate(CurrentProject.Path & "\11_accLonLat.html")
End Sub
'-----------------------------------------------------------------------------------
Private Sub oMsWebBrowser_DocumentComplete(ByVal pDisp As Object, URL As Variant)
	' ustaw odwpłania do obiektów
	Set oDocument = Me.oMsWebBrowser.Document
	With oDocument
		Set oDivMap = .getElementById("basicMap")
		Set oInputLon = .getElementById("lonClick")
		Set oInputLat = .getElementById("latClick")
	End With
End Sub

Zdarzenia 'click' dblclick','movestart', 'move', 'moveend' generowane przez mapę

Opis podstawowych zdarzeń zachodzących nad mapą:

'click'
zostaje wciśnięty i puszczony lewy przycisk myszy (LPM Down; LPM Up). Dla takiego zdarzenia w łatwy sposób możemy odczytać współrzędne Lon Lat klikniętego punktu.
'dblclick'
dwa szybko następujące po sobie zdarzenia wciśnięcia i puszczenia lewego przycisku myszy (LPM Down; LPM Up).
Mapa odbiera takie zdarzenie jako polecenie powiększenia o 1 stopień mapy (zoom mapy).
Formant oMsWebBrowser generuje najpierw zdarzenie 'click' i następnie 'dblclick', więc odczyt współrzędnych Lon Lat tak klikniętego punktu jest całkowicie mylący.
'movestart',
zostaje wciśnięty i przytrzymywany lewy przycisk myszy (LPM Down), a myszka będzie przesuwana,
move'
przytrzymywany jest lewy przycisk myszy (LPM Down), a myszka jest przesuwana
'moveend'
po zakończeniu przesuwania myszki zostaje zwolniony lewy przycisk myszy (LPM Up)
'movestart', move', 'moveend'
Mapa odbiera takie zdarzenie jako polecenie przesunięcia obszaru mapy, zgodnie z kierunkiem przesunięcia.
Formant oMsWebBrowser generuje zdarzenie 'click', więc i w tym przypadku odczyt współrzędnych Lon Lat tak „klikniętego” punktu jest całkowicie mylący.

Jak rozpoznać zdarzenie 'click' generowane nad mapą?

Funkcja Private Function oDivMap_onclick() As Boolean wywołowana jest przez po kliknięciu, po podwójnym kliknięciu, a także po przesuwaniu myszy z wciśniętym lewym przyciskiem myszy i puszczeniu lewego przycisku myszy. Aby wywołać funkcję rysującą znacznik (marker) w klikniętym punkcie mapy musimy jednoznacznie rozpoznać pojedyncze kliknięcie nad mapą (bez przesuwania myszy). Poniżej zamieszczam kod realizujący ten cel. Końcowe rozpoznanie, czy zaszło zdarzenie 'onclick', wykonywane jest w Timerze Formularza z ok. 300 milisekundowym opóźnieniem. Sprawdzana tam jest wartośći formantu Me.txtClickLon, które tylko w przypadku gdy zaszło zdarzenie 'click', zawiera wartość współrzędnej Lon.

' Zdarzenie oDivMap_onclick() jest generowane przy przesuwaniu mapy,
' (wciśnięty LPM => przesunięcie mapy => puszczony LPM).
' Także zdarzenie 'ondblclick' generuje najpierw zdarzenie 'onclick'
' na poziomie formularza i formantu Me.oMsWebBrowser.
' Biblioteka OpenLayers prawidłowo reaguje na 'onclick' oraz
' 'ondblclick' i 'onmousemove'.
Private Function oDivMap_onclick() As Boolean
Dim sLonCur     As String ' bieżąca wartość Lon
Dim sLatCur     As String ' bieżąca wartość Lat
Static sLonOld  As String ' poprzednia wartość Lon
Static sLatOld  As String ' poprzednia wartość Lon

  ' pobierz z ukrytych pól typu <input> współrzędne Lon i Lat
  sLonCur = oInputLon.Value
  sLatCur = oInputLat.Value
 
  ' w OpenLayers (na mapie) nie zaszło zdarzenie Click
  '(współrzędne Lon oraz Lat są takie same)
  If sLonCur = sLonOld And sLatCur = sLatOld Then
    ' wyzeruj formant Me.txtClickLon, by w zdarzeniu Form_Timer()
    ' można było sprawdzić, czy zaszło zdarzenie 'dblclick'
    Me.txtClickLon.Value = ""
    Me.txtClickLat.Value = "Przesunięcie mapy"
  Else
    ' zapamiętaj współrzędne
    sLonOld = sLonCur 'oInputLon.Value
    sLatOld = sLatCur 'oInputLat.Value
    ' zapisz współrzedne
    Me.txtClickLon.Value = sLonOld
    Me.txtClickLat.Value = sLatOld
    
    Me.TimerInterval = 300
    ' funkcja tworząca marker musi być uruchomiona w Timerze,
    ' gdyż 'ondblclick' i 'onmousemove' jest rozpoznawany jako 'onclick'
    ' Call fCreateMarker(.....)
  End If
End Function

Private Function oDivMap_ondblclick() As Boolean
  ' wyzeruj formant Me.txtClickLon, by w zdarzeniu Form_Timer()
  ' można było sprawdzić, czy zaszło zdarzenie 'dblclick'
  Me.txtClickLon.Value = ""
  Me.txtClickLat.Value = "'dblClick' => zoom"
End Function

Private Sub Form_Timer()
  Me.TimerInterval = 0
  ' jeżeli formant Me.txtClickLon nie został wyzerowany zaszło zdarzenie 'onclick'
  If Len(Nz(Me.txtClickLon.Value, "")) > 0 Then
    Call fCreateMarker("MojMarker", Me.txtClickLon.Value, _
                       Me.txtClickLat.Value, m_sMarkerName, _
                       48, 48, (-48 / 2), -48, _
                       "Miejsce 01.", "To jest najciekawsze miejsce, " & _
                       "które bezwzględnie należy odwiedzić.", _
                       "foto/03_thm.jpg", "fot.1 Stara chata")
  End If
End Sub

Znacznik (marker) w miejscu kliknięcia mapy. 'click' generowane nad mapą?

O tym jak utworzyć marker (znacznik), w jaki sposób wyświetlić okienko popup (dymek) z dokładniejszymi informacjami o punkcie wskazywanym przez znacznik (marker), o zdarzeniach generowanych przez znacznik (marker) oraz ich rejestrowaniu i obsłudze zdarzeń pisałem na stronie: Znaczniki - okienko popu.

Znacznik w miejscu kliknięcia
4. Współrzędne Lon Lat punktu wskazywanego przez (znacznik) marker.


Znak Informacja dodatkowa Pliki znaczników graficznych pobrano ze strony
www.icons-land.com 


Niespodziewany błąd dla formularza bez przycisku Min lub Max, bądź obu przycisków.


Jeżeli ustawimy właściwość formularza Form.MinMaxButtons na inną niż Oba włączone,
lub Form.CloseButton = Nie, bądź Form.ControlBox = Nie

to przy próbie przełączenia się w „Tryb projektowania” (Widok projekt) formularza pojawia się komunikat o błędzie:


Znacznik w miejscu kliknięcia
5. Komunikat o błędzie wyrażenia 'StatusTextChange'.