Browse Source

Initial Commit

daniel 5 months ago
commit
4b47cd920d

+ 8 - 0
.idea/.gitignore

@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml

+ 1 - 0
.idea/.name

@@ -0,0 +1 @@
+leaflet Kopie.js

+ 10 - 0
.idea/chatBotDemo.iml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$" />
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="library" name="leaflet" level="application" />
+    <orderEntry type="library" name="map" level="application" />
+  </component>
+</module>

+ 16 - 0
.idea/deployment.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="PublishConfigData" autoUpload="Always" serverName="area51.cybob.com" remoteFilesAllowedToDisappearOnAutoupload="false" confirmBeforeUploading="false" autoUploadExternalChanges="true">
+    <option name="confirmBeforeUploading" value="false" />
+    <serverData>
+      <paths name="area51.cybob.com">
+        <serverdata>
+          <mappings>
+            <mapping deploy="/" local="$PROJECT_DIR$" web="/praktikum/daniel" />
+          </mappings>
+        </serverdata>
+      </paths>
+    </serverData>
+    <option name="myAutoUpload" value="ALWAYS" />
+  </component>
+</project>

+ 6 - 0
.idea/jsLibraryMappings.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JavaScriptLibraryMappings">
+    <file url="PROJECT" libraries="{leaflet, map}" />
+  </component>
+</project>

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/chatBotDemo.iml" filepath="$PROJECT_DIR$/.idea/chatBotDemo.iml" />
+    </modules>
+  </component>
+</project>

+ 19 - 0
.idea/php.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="MessDetectorOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PHPCSFixerOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PHPCodeSnifferOptionsConfiguration">
+    <option name="highlightLevel" value="WARNING" />
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PhpStanOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PsalmOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+</project>

+ 14 - 0
.idea/webServers.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="WebServers">
+    <option name="servers">
+      <webServer id="cdb70ea0-dff5-4221-afa6-9077ffbc46f2" name="area51.cybob.com" url="http://area51.cybob.com">
+        <fileTransfer host="area51.cybob.com" port="21">
+          <advancedOptions>
+            <advancedOptions dataProtectionLevel="Private" passiveMode="true" shareSSLContext="true" />
+          </advancedOptions>
+        </fileTransfer>
+      </webServer>
+    </option>
+  </component>
+</project>

+ 240 - 0
agent/css/map.css

@@ -0,0 +1,240 @@
+#map-wrapper {
+    width: 100%;
+    max-width: 100%;
+    border: 1px solid #ccc;
+    border-radius: 8px;
+    overflow: hidden;
+    position:relative;
+}
+
+#map {
+    height: 550px;
+    width: 100%;
+}
+
+#map-info {
+    position: absolute;
+    padding: 8px;
+    border: 0 solid #ccc;
+    border-radius: 16px;
+    background-color: rgba(255,255,255,0.8);
+    backdrop-filter: blur(5px);
+    overflow: hidden;
+    z-index: 999;
+    right: -8px;
+    /* Todo anders machen nicht mit scale */
+    transform: scale(0.8);
+    max-height: 200px;
+    visibility:hidden;
+}
+
+.compact {
+    height: 400px !important;
+}
+
+#map-wrapper.compact .map,
+#map-wrapper.compact {
+    height: 400px !important;
+}
+
+#map-wrapper.compact #map-info {
+    top: auto !important;
+    bottom: 0 !important;
+    left: 0 !important;
+    right: 0 !important;
+    width: 100%;
+    transform: none !important;
+    border-radius: 0px 0px 0 0 !important;
+    padding:12px;
+    max-height: 450px;
+}
+
+#map-wrapper.compact #map-info {
+    font-size: 14px;
+    line-height: 1.3;
+}
+
+#map-wrapper.compact #map-info h4 {
+    font-size: 16px;
+}
+
+#map-wrapper.compact #map-info span,
+#map-wrapper.compact #map-info a {
+    font-size: 13px;
+}
+
+#map-wrapper.compact #map-info a {
+    text-decoration: none;
+}
+
+#map-wrapper.compact #map-info-opening-hours-weekdays {
+    font-size: 13px;
+}
+
+#map-wrapper.compact .leaflet-marker-icon {
+    width: 28px !important;
+    height: 28px !important;
+    background-size: cover;
+}
+
+#map-info.active {
+    animation: fadeIn 0.125s ease-in forwards;
+}
+
+#map-info.closing {
+    animation: fadeOut 0.125s ease-in forwards;
+}
+
+.input-width-clear {
+    position: relative;
+    display: inline-block;
+}
+
+#map-info-close,
+#zipcode-search-close{
+    float: right;
+    font-size: 18px;
+    font-weight: bold;
+    cursor: pointer;
+    user-select: none;
+    -webkit-user-select: none;
+    -ms-user-select: none;
+    padding: 4px 8px;
+    line-height: 1;
+    color: #333;
+    border: 0px solid transparent;
+    background-color: rgba(0, 0, 0, 0);
+    transition: transform 0.2s ease;
+}
+
+#map-info-close:hover {
+    transform: scale(1.05)
+}
+
+#zipcode-search-close {
+    position: absolute;
+    top: 25%;
+    right: 25%;
+}
+
+#zipcode-search-wrapper {
+    display: flex;
+    align-items: center;
+    margin: 10px 0;
+    justify-content: center;
+    position: absolute;
+    z-index: 9999;
+    margin-left: 56px;
+}
+
+#zipcode-input {
+    padding: 8px 12px;
+    border-radius: 8px;
+    border: 1px solid #ccc;
+    width: 120px;
+    font-size: 15px;
+    transition: border-color 0.2s ease, box-shadow 0.2s ease;
+    background-color: #fff;
+    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+    pointer-events: auto;
+    cursor: auto;
+}
+
+#zipcode-input:focus {
+    outline: none;
+    box-shadow: 0 0 4px rgba(0,120,70,0.25);
+    pointer-events: auto;
+}
+
+svg {
+    color: gray;
+}
+
+.input-close {
+    margin-right: 4px;
+    transform: scale(0.8);
+}
+
+#zipcode-search-button {
+    padding: 6px 10px;
+    border: 1px solid #ccc;
+    border-radius: 6px;
+    background-color: #f3f3f3;
+    cursor: pointer;
+    transition: background-color 0.2s ease, transform 0.2s ease;
+    margin-left: 4px;
+}
+
+#zipcode-search-button:hover {
+    background-color: #e6e6e6;
+    transform: scale(1.05);
+    cursor: auto;
+}
+
+/* leaflet customization*/
+
+/*Top Left plus and minus*/
+.leaflet-control {
+    border: 0px solid black !important;
+    box-shadow: 0px 0px 5px rgba(0,0,0,0.15);
+    background-color: rgba(255,255,255,0.8);
+    backdrop-filter: blur(5px);
+}
+
+.leaflet-bar a {
+    background-color: rgba(0,0,0,0);
+}
+
+
+.awigo-marker-icon {
+    /*transition: transform 0.2s ease, box-shadow 0.2s ease;*/
+    width:41px !important;
+    background-size: cover;
+}
+
+.hidden{
+    visibility: hidden !important;
+}
+
+.awigo-marker-icon + .leaflet-marker-shadow {
+    /*display: none !important;*/
+}
+
+.leaflet-marker-icon:hover {
+    /*box-shadow: 0px 0px 5px rgba(0,0,0,0.3);*/
+    /*border-radius: 5px;*/
+}
+
+.input-shake-error {
+    animation: shake 0.3s ease-in;
+    border-color: red;
+}
+
+/*animations*/
+@keyframes fadeIn {
+    from {
+        opacity: 0;
+        visibility: visible;
+    }
+    to {
+        opacity: 1;
+        visibility: visible;
+    }
+}
+
+@keyframes fadeOut {
+    from {
+        opacity: 1;
+        visibility: visible;
+    }
+    to {
+        opacity: 0;
+        visibility: hidden;
+    }
+}
+
+@keyframes shake {
+    0%, 100% { transform: translateX(0); }
+    20%, 60% { transform: translateX(-4px); }
+    40%, 80% { transform: translateX(4px); }
+}

+ 498 - 0
agent/data/grünplätze_awigo.json

@@ -0,0 +1,498 @@
+[
+  {
+    "id": "420",
+    "title": "Grünplatz Bad Essen",
+    "category": "Grünplatz",
+    "lng": "8.372720353828413",
+    "lat": "52.324769577551656",
+    "zip": "49152",
+    "city": "Bad Essen",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Forststraße",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "421",
+    "title": "Grünplatz Bad Iburg",
+    "category": "Grünplatz",
+    "lng": "8.042813157008368",
+    "lat": "52.14601955239405",
+    "zip": "49186",
+    "city": "Bad Iburg",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Kreienbrink",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "422",
+    "title": "Grünplatz Bad Laer",
+    "category": "Grünplatz",
+    "lng": "8.078037249182103",
+    "lat": "52.099199643413385",
+    "zip": "49196",
+    "city": "Bad Laer",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Westkamp",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "423",
+    "title": "Grünplatz Bad Rothenfelde-Strang",
+    "category": "Grünplatz",
+    "lng": "8.170396847425422",
+    "lat": "52.09144423612593",
+    "zip": "49214",
+    "city": "Bad Rothenfelde",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Sundernweg",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "424",
+    "title": "Grünplatz Belm",
+    "category": "Grünplatz",
+    "lng": "8.159185578473625",
+    "lat": "52.3367395908747",
+    "zip": "49191",
+    "city": "Belm",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Osterberg",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "425",
+    "title": "Grünplatz Woltrup-Wehbergen",
+    "category": "Grünplatz",
+    "lng": "7.940258681774139",
+    "lat": "52.52614692216058",
+    "zip": "49593",
+    "city": "Bersenbrück",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Wehberger Straße",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "426",
+    "title": "Grünplatz Bippen",
+    "category": "Grünplatz",
+    "lng": "7.741834601991286",
+    "lat": "52.592147322387184",
+    "zip": "49626",
+    "city": "Bippen",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Berger Straße",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "427",
+    "title": "Grünplatz Bissendorf-Jeggen",
+    "category": "Grünplatz",
+    "lng": "8.19539812503126",
+    "lat": "52.26552905102797",
+    "zip": "49143",
+    "city": "Bissendorf",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Niederfeldweg",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "428",
+    "title": "Grünplatz Bohmte",
+    "category": "Grünplatz",
+    "lng": "8.302253943902542",
+    "lat": "52.36115642713029",
+    "zip": "49163",
+    "city": "Bohmte",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Maschweg",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "429",
+    "title": "Grünplatz Lappenstuhl",
+    "category": "Grünplatz",
+    "lng": "8.035113966717518",
+    "lat": "52.41234977950307",
+    "zip": "49565",
+    "city": "Bramsche",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Lutterdamm",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "430",
+    "title": "Grünplatz Hesepe",
+    "category": "Grünplatz",
+    "lng": "7.958734950648477",
+    "lat": "52.4366721572488",
+    "zip": "49565",
+    "city": "Bramsche",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Mühlenweg",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "431",
+    "title": "Grünplatz Bramscher Berg",
+    "category": "Grünplatz",
+    "lng": "7.972366506884782",
+    "lat": "52.402346805666525",
+    "zip": "49565",
+    "city": "Bramsche",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Zu den Lohwiesen",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "432",
+    "title": "Grünplatz Fürstenau",
+    "category": "Grünplatz",
+    "lng": "7.693396300108361",
+    "lat": "52.513318637097605",
+    "zip": "49584",
+    "city": "Fürstenau",
+    "telephone": "",
+    "telephone_uf": "",
+    "address": "Utdrift",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "433",
+    "title": "Grünplatz Oesede",
+    "category": "Grünplatz",
+    "lng": "8.073033392429352",
+    "lat": "52.1963165582363",
+    "zip": "49124",
+    "city": "Georgsmarienhütte",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Am Wiesenbach",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "434",
+    "title": "Grünplatz Alt-Georgsmarienhütte",
+    "category": "Grünplatz",
+    "lng": "8.037705426060484",
+    "lat": "52.211691759466646",
+    "zip": "49124",
+    "city": "Georgsmarienhütte",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Malberger Straße",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "435",
+    "title": "Grünplatz Glandorf",
+    "category": "Grünplatz",
+    "lng": "8.009309470653534",
+    "lat": "52.08552884457688",
+    "zip": "49219",
+    "city": "Glandorf",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Krankenhausstraße",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "436",
+    "title": "Grünplatz Hagen a.T.W.",
+    "category": "Grünplatz",
+    "lng": "7.957224133590657",
+    "lat": "52.193619762668355",
+    "zip": "49170",
+    "city": "Hagen a.T.W.",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Loheiden Knapp",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "437",
+    "title": "Grünplatz Hasbergen",
+    "category": "Grünplatz",
+    "lng": "7.956618757247156",
+    "lat": "52.240766173376294",
+    "zip": "49205",
+    "city": "Hasbergen",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Schulstraße",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "438",
+    "title": "Grünplatz Hankenberge",
+    "category": "Grünplatz",
+    "lng": "8.159658014774323",
+    "lat": "52.15858039947395",
+    "zip": "49176",
+    "city": "Hilter a.T.W.",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Borgloher Straße",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "439",
+    "title": "Grünplatz Melle-Markendorf",
+    "category": "Grünplatz",
+    "lng": "8.304112361877007",
+    "lat": "52.20696559066741",
+    "zip": "49328",
+    "city": "Melle",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Bulstener Straße",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "440",
+    "title": "Grünplatz Drantum",
+    "category": "Grünplatz",
+    "lng": "8.30450900167466",
+    "lat": "52.20709746584891",
+    "zip": "49324",
+    "city": "Melle",
+    "telephone": "",
+    "telephone_uf": "",
+    "address": "Gesmolder Straße ",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "441",
+    "title": "Grünplatz Melle-Neuenkirchen",
+    "category": "Grünplatz",
+    "lng": "8.400603407270864",
+    "lat": "52.15273832788033",
+    "zip": "49326",
+    "city": "Melle",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Rahdenstraße ",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "442",
+    "title": "Grünplatz Melle-Wellingholzhausen",
+    "category": "Grünplatz",
+    "lng": "8.269660770893097",
+    "lat": "52.16627233736274",
+    "zip": "49326",
+    "city": "Melle",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Wellingholzhausener Straße ",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "443",
+    "title": "Grünplatz Neuenkirchen-Südmerzen",
+    "category": "Grünplatz",
+    "lng": "7.817016541957855",
+    "lat": "52.4439715937447",
+    "zip": "49586",
+    "city": "Merzen",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Voltlager Damm",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "444",
+    "title": "Grünplatz Nortrup",
+    "category": "Grünplatz",
+    "lng": "7.882755",
+    "lat": "52.636527",
+    "zip": "49638",
+    "city": "Nortrup",
+    "telephone": "",
+    "telephone_uf": "",
+    "address": "Ahorster Damm",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "445",
+    "title": "Grünplatz Quakenbrück",
+    "category": "Grünplatz",
+    "lng": "7.948037087917328",
+    "lat": "52.662823416273945",
+    "zip": "49610",
+    "city": "Quakenbrück",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Niedersachsenstraße",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "446",
+    "title": "Grünplatz Lechtingen",
+    "category": "Grünplatz",
+    "lng": "8.030607683714265",
+    "lat": "52.329320099090396",
+    "zip": "49134",
+    "city": "Wallenhorst",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Schulweg",
+    "distance": "null",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "998",
+    "title": "Grünplatz Ankum auf Recyclinghof",
+    "category": "Grünplatz (auf Recyclinghof)",
+    "lng": "7.846577167510986",
+    "lat": "52.551312691150194",
+    "zip": "49577",
+    "city": "Ankum",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Bippener Straße 32",
+    "distance": "null",
+    "openingTimes": "Mo, Di, Mi, Do, Fr: 08:00 - 18:00 Uhr\nSa: 08:00 - 13:30 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "999",
+    "title": "Grünplatz Wallenhorst (auf Recyclinghofneubau)",
+    "category": "Grünplatz (auf Recyclinghof)",
+    "lng": "7.995343208312988",
+    "lat": "52.36196045646282",
+    "zip": "49134",
+    "city": "Wallenhorst",
+    "telephone": "",
+    "telephone_uf": "",
+    "address": "Schwarzer See 17",
+    "distance": "null",
+    "openingTimes": "Mo, Di, Mi, Do, Fr: 08:00 - 18:00 Uhr\nSa: 08:00 - 13:30 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "1000",
+    "title": "Grünplatz Schwagstorf auf Recyclinghof",
+    "category": "Grünplatz (auf Recyclinghof)",
+    "lng": "8.22378158569336",
+    "lat": "52.35964106210497",
+    "zip": "49179",
+    "city": "Ostercappeln",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Schwagstorfer Eue 8",
+    "distance": "null",
+    "openingTimes": "Mo, Di, Mi, Do, Fr: 08:00 - 18:00 Uhr\nSa: 08:00 - 13:30 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  },
+  {
+    "id": "1001",
+    "title": "Grünplatz Gesmold auf Recyclinghof",
+    "category": "Grünplatz (auf Recyclinghof)",
+    "lng": "8.263907432556152",
+    "lat": "52.21675764836974",
+    "zip": "49326",
+    "city": "Melle",
+    "telephone": "(0 54 01) 36 55 55",
+    "telephone_uf": "05401365555",
+    "address": "Im Gewerbepark 19",
+    "distance": "null",
+    "openingTimes": "Mo, Di, Mi, Do, Fr: 08:00 - 18:00 Uhr\nSa: 08:00 - 13:30 Uhr",
+    "openingStateClass": "closing_soon",
+    "image": "https://www.awigo.de/fileadmin/media/images/icons/negativ/awigo-icon-gruenplatz-map.png"
+  }
+]

+ 22 - 0
agent/data/map_template_data.json

@@ -0,0 +1,22 @@
+[
+  {
+    "title": "Cybob GmbH",
+    "lng": "8.0472",
+    "lat": "52.2799",
+    "zip": "49088",
+    "city": "Osnabrück",
+    "telephone": "+49 123 4567890",
+    "address": "Kampstraße 30",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr"
+  },
+  {
+    "title": "Map Marker Example",
+    "lng": "8.0572",
+    "lat": "52.3799",
+    "zip": "49088",
+    "city": "Osnabrück",
+    "telephone": "+49 123 4567890",
+    "address": "Kampstraße 30",
+    "openingTimes": "Mo, Mi, Fr: 14:00 - 18:00 Uhr\nSa: 09:30 - 15:00 Uhr"
+  }
+]

+ 4 - 0
agent/images/at.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
+    <path d="M4.146 8.354a.5.5 0 0 1 0-.708L9.793 2H6.5a.5.5 0 0 1 0-1h4a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V2.707L4.854 8.854a.5.5 0 0 1-.708 0z"/>
+    <path d="M13.5 14a.5.5 0 0 1-.5.5H3a2 2 0 0 1-2-2V3.5a.5.5 0 0 1 1 0V12.5a1 1 0 0 0 1 1h10a.5.5 0 0 1 .5.5z"/>
+</svg>

BIN
agent/images/awigo-icon-gruenplatz-map.png


BIN
agent/images/marker-icon-default-green-2x.png


BIN
agent/images/marker-icon-default-green.png


BIN
agent/images/marker-icon-shadow-default.png


BIN
agent/images/marker-icon.png


+ 4 - 0
agent/images/x.svg

@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+    <line x1="1" y1="1" x2="15" y2="15" stroke="currentColor" stroke-width="2"/>
+    <line x1="15" y1="1" x2="1" y2="15" stroke="currentColor" stroke-width="2"/>
+</svg>

+ 661 - 0
agent/leaflet/leaflet.css

@@ -0,0 +1,661 @@
+/* required styles */
+
+.leaflet-pane,
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-tile-container,
+.leaflet-pane > svg,
+.leaflet-pane > canvas,
+.leaflet-zoom-box,
+.leaflet-image-layer,
+.leaflet-layer {
+    position: absolute;
+    left: 0;
+    top: 0;
+}
+.leaflet-container {
+    overflow: hidden;
+}
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    user-select: none;
+    -webkit-user-drag: none;
+}
+/* Prevents IE11 from highlighting tiles in blue */
+.leaflet-tile::selection {
+    background: transparent;
+}
+/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
+.leaflet-safari .leaflet-tile {
+    image-rendering: -webkit-optimize-contrast;
+}
+/* hack that prevents hw layers "stretching" when loading new tiles */
+.leaflet-safari .leaflet-tile-container {
+    width: 1600px;
+    height: 1600px;
+    -webkit-transform-origin: 0 0;
+}
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+    display: block;
+}
+/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
+/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
+.leaflet-container .leaflet-overlay-pane svg {
+    max-width: none !important;
+    max-height: none !important;
+}
+.leaflet-container .leaflet-marker-pane img,
+.leaflet-container .leaflet-shadow-pane img,
+.leaflet-container .leaflet-tile-pane img,
+.leaflet-container img.leaflet-image-layer,
+.leaflet-container .leaflet-tile {
+    max-width: none !important;
+    max-height: none !important;
+    width: auto;
+    padding: 0;
+}
+
+.leaflet-container img.leaflet-tile {
+    /* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
+    mix-blend-mode: plus-lighter;
+}
+
+.leaflet-container.leaflet-touch-zoom {
+    -ms-touch-action: pan-x pan-y;
+    touch-action: pan-x pan-y;
+}
+.leaflet-container.leaflet-touch-drag {
+    -ms-touch-action: pinch-zoom;
+    /* Fallback for FF which doesn't support pinch-zoom */
+    touch-action: none;
+    touch-action: pinch-zoom;
+}
+.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
+    -ms-touch-action: none;
+    touch-action: none;
+}
+.leaflet-container {
+    -webkit-tap-highlight-color: transparent;
+}
+.leaflet-container a {
+    -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
+}
+.leaflet-tile {
+    filter: inherit;
+    visibility: hidden;
+}
+.leaflet-tile-loaded {
+    visibility: inherit;
+}
+.leaflet-zoom-box {
+    width: 0;
+    height: 0;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    z-index: 800;
+}
+/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
+.leaflet-overlay-pane svg {
+    -moz-user-select: none;
+}
+
+.leaflet-pane         { z-index: 400; }
+
+.leaflet-tile-pane    { z-index: 200; }
+.leaflet-overlay-pane { z-index: 400; }
+.leaflet-shadow-pane  { z-index: 500; }
+.leaflet-marker-pane  { z-index: 600; }
+.leaflet-tooltip-pane   { z-index: 650; }
+.leaflet-popup-pane   { z-index: 700; }
+
+.leaflet-map-pane canvas { z-index: 100; }
+.leaflet-map-pane svg    { z-index: 200; }
+
+.leaflet-vml-shape {
+    width: 1px;
+    height: 1px;
+}
+.lvml {
+    behavior: url(#default#VML);
+    display: inline-block;
+    position: absolute;
+}
+
+
+/* control positioning */
+
+.leaflet-control {
+    position: relative;
+    z-index: 800;
+    pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
+    pointer-events: auto;
+}
+.leaflet-top,
+.leaflet-bottom {
+    position: absolute;
+    z-index: 1000;
+    pointer-events: none;
+}
+.leaflet-top {
+    top: 0;
+}
+.leaflet-right {
+    right: 0;
+}
+.leaflet-bottom {
+    bottom: 0;
+}
+.leaflet-left {
+    left: 0;
+}
+.leaflet-control {
+    float: left;
+    clear: both;
+}
+.leaflet-right .leaflet-control {
+    float: right;
+}
+.leaflet-top .leaflet-control {
+    margin-top: 10px;
+}
+.leaflet-bottom .leaflet-control {
+    margin-bottom: 10px;
+}
+.leaflet-left .leaflet-control {
+    margin-left: 10px;
+}
+.leaflet-right .leaflet-control {
+    margin-right: 10px;
+}
+
+
+/* zoom and fade animations */
+
+.leaflet-fade-anim .leaflet-popup {
+    opacity: 0;
+    -webkit-transition: opacity 0.2s linear;
+    -moz-transition: opacity 0.2s linear;
+    transition: opacity 0.2s linear;
+}
+.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
+    opacity: 1;
+}
+.leaflet-zoom-animated {
+    -webkit-transform-origin: 0 0;
+    -ms-transform-origin: 0 0;
+    transform-origin: 0 0;
+}
+svg.leaflet-zoom-animated {
+    will-change: transform;
+}
+
+.leaflet-zoom-anim .leaflet-zoom-animated {
+    -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
+    -moz-transition:    -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
+    transition:         transform 0.25s cubic-bezier(0,0,0.25,1);
+}
+.leaflet-zoom-anim .leaflet-tile,
+.leaflet-pan-anim .leaflet-tile {
+    -webkit-transition: none;
+    -moz-transition: none;
+    transition: none;
+}
+
+.leaflet-zoom-anim .leaflet-zoom-hide {
+    visibility: hidden;
+}
+
+
+/* cursors */
+
+.leaflet-interactive {
+    cursor: pointer;
+}
+.leaflet-grab {
+    cursor: -webkit-grab;
+    cursor:    -moz-grab;
+    cursor:         grab;
+}
+.leaflet-crosshair,
+.leaflet-crosshair .leaflet-interactive {
+    cursor: crosshair;
+}
+.leaflet-popup-pane,
+.leaflet-control {
+    cursor: auto;
+}
+.leaflet-dragging .leaflet-grab,
+.leaflet-dragging .leaflet-grab .leaflet-interactive,
+.leaflet-dragging .leaflet-marker-draggable {
+    cursor: move;
+    cursor: -webkit-grabbing;
+    cursor:    -moz-grabbing;
+    cursor:         grabbing;
+}
+
+/* marker & overlays interactivity */
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-image-layer,
+.leaflet-pane > svg path,
+.leaflet-tile-container {
+    pointer-events: none;
+}
+
+.leaflet-marker-icon.leaflet-interactive,
+.leaflet-image-layer.leaflet-interactive,
+.leaflet-pane > svg path.leaflet-interactive,
+svg.leaflet-image-layer.leaflet-interactive path {
+    pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
+    pointer-events: auto;
+}
+
+/* visual tweaks */
+
+.leaflet-container {
+    background: #ddd;
+    outline-offset: 1px;
+}
+.leaflet-container a {
+    color: #0078A8;
+}
+.leaflet-zoom-box {
+    border: 2px dotted #38f;
+    background: rgba(255,255,255,0.5);
+}
+
+
+/* general typography */
+.leaflet-container {
+    font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
+    font-size: 12px;
+    font-size: 0.75rem;
+    line-height: 1.5;
+}
+
+
+/* general toolbar styles */
+
+.leaflet-bar {
+    box-shadow: 0 1px 5px rgba(0,0,0,0.65);
+    border-radius: 4px;
+}
+.leaflet-bar a {
+    background-color: #fff;
+    border-bottom: 1px solid #ccc;
+    width: 26px;
+    height: 26px;
+    line-height: 26px;
+    display: block;
+    text-align: center;
+    text-decoration: none;
+    color: black;
+}
+.leaflet-bar a,
+.leaflet-control-layers-toggle {
+    background-position: 50% 50%;
+    background-repeat: no-repeat;
+    display: block;
+}
+.leaflet-bar a:hover,
+.leaflet-bar a:focus {
+    background-color: #f4f4f4;
+}
+.leaflet-bar a:first-child {
+    border-top-left-radius: 4px;
+    border-top-right-radius: 4px;
+}
+.leaflet-bar a:last-child {
+    border-bottom-left-radius: 4px;
+    border-bottom-right-radius: 4px;
+    border-bottom: none;
+}
+.leaflet-bar a.leaflet-disabled {
+    cursor: default;
+    background-color: #f4f4f4;
+    color: #bbb;
+}
+
+.leaflet-touch .leaflet-bar a {
+    width: 30px;
+    height: 30px;
+    line-height: 30px;
+}
+.leaflet-touch .leaflet-bar a:first-child {
+    border-top-left-radius: 2px;
+    border-top-right-radius: 2px;
+}
+.leaflet-touch .leaflet-bar a:last-child {
+    border-bottom-left-radius: 2px;
+    border-bottom-right-radius: 2px;
+}
+
+/* zoom control */
+
+.leaflet-control-zoom-in,
+.leaflet-control-zoom-out {
+    font: bold 18px 'Lucida Console', Monaco, monospace;
+    text-indent: 1px;
+}
+
+.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out  {
+    font-size: 22px;
+}
+
+
+/* layers control */
+
+.leaflet-control-layers {
+    box-shadow: 0 1px 5px rgba(0,0,0,0.4);
+    background: #fff;
+    border-radius: 5px;
+}
+.leaflet-control-layers-toggle {
+    background-image: url(../images/layers.png);
+    width: 36px;
+    height: 36px;
+}
+.leaflet-retina .leaflet-control-layers-toggle {
+    background-image: url(../images/layers-2x.png);
+    background-size: 26px 26px;
+}
+.leaflet-touch .leaflet-control-layers-toggle {
+    width: 44px;
+    height: 44px;
+}
+.leaflet-control-layers .leaflet-control-layers-list,
+.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
+    display: none;
+}
+.leaflet-control-layers-expanded .leaflet-control-layers-list {
+    display: block;
+    position: relative;
+}
+.leaflet-control-layers-expanded {
+    padding: 6px 10px 6px 6px;
+    color: #333;
+    background: #fff;
+}
+.leaflet-control-layers-scrollbar {
+    overflow-y: scroll;
+    overflow-x: hidden;
+    padding-right: 5px;
+}
+.leaflet-control-layers-selector {
+    margin-top: 2px;
+    position: relative;
+    top: 1px;
+}
+.leaflet-control-layers label {
+    display: block;
+    font-size: 13px;
+    font-size: 1.08333em;
+}
+.leaflet-control-layers-separator {
+    height: 0;
+    border-top: 1px solid #ddd;
+    margin: 5px -10px 5px -6px;
+}
+
+/* Default icon URLs */
+.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
+    background-image: url(../images/marker-icon.png);
+}
+
+
+/* attribution and scale controls */
+
+.leaflet-container .leaflet-control-attribution {
+    background: #fff;
+    background: rgba(255, 255, 255, 0.8);
+    margin: 0;
+}
+.leaflet-control-attribution,
+.leaflet-control-scale-line {
+    padding: 0 5px;
+    color: #333;
+    line-height: 1.4;
+}
+.leaflet-control-attribution a {
+    text-decoration: none;
+}
+.leaflet-control-attribution a:hover,
+.leaflet-control-attribution a:focus {
+    text-decoration: underline;
+}
+.leaflet-attribution-flag {
+    display: inline !important;
+    vertical-align: baseline !important;
+    width: 1em;
+    height: 0.6669em;
+}
+.leaflet-left .leaflet-control-scale {
+    margin-left: 5px;
+}
+.leaflet-bottom .leaflet-control-scale {
+    margin-bottom: 5px;
+}
+.leaflet-control-scale-line {
+    border: 2px solid #777;
+    border-top: none;
+    line-height: 1.1;
+    padding: 2px 5px 1px;
+    white-space: nowrap;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    background: rgba(255, 255, 255, 0.8);
+    text-shadow: 1px 1px #fff;
+}
+.leaflet-control-scale-line:not(:first-child) {
+    border-top: 2px solid #777;
+    border-bottom: none;
+    margin-top: -2px;
+}
+.leaflet-control-scale-line:not(:first-child):not(:last-child) {
+    border-bottom: 2px solid #777;
+}
+
+.leaflet-touch .leaflet-control-attribution,
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+    box-shadow: none;
+}
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+    border: 2px solid rgba(0,0,0,0.2);
+    background-clip: padding-box;
+}
+
+
+/* popup */
+
+.leaflet-popup {
+    position: absolute;
+    text-align: center;
+    margin-bottom: 20px;
+}
+.leaflet-popup-content-wrapper {
+    padding: 1px;
+    text-align: left;
+    border-radius: 12px;
+}
+.leaflet-popup-content {
+    margin: 13px 24px 13px 20px;
+    line-height: 1.3;
+    font-size: 13px;
+    font-size: 1.08333em;
+    min-height: 1px;
+}
+.leaflet-popup-content p {
+    margin: 17px 0;
+    margin: 1.3em 0;
+}
+.leaflet-popup-tip-container {
+    width: 40px;
+    height: 20px;
+    position: absolute;
+    left: 50%;
+    margin-top: -1px;
+    margin-left: -20px;
+    overflow: hidden;
+    pointer-events: none;
+}
+.leaflet-popup-tip {
+    width: 17px;
+    height: 17px;
+    padding: 1px;
+
+    margin: -10px auto 0;
+    pointer-events: auto;
+
+    -webkit-transform: rotate(45deg);
+    -moz-transform: rotate(45deg);
+    -ms-transform: rotate(45deg);
+    transform: rotate(45deg);
+}
+.leaflet-popup-content-wrapper,
+.leaflet-popup-tip {
+    background: white;
+    color: #333;
+    box-shadow: 0 3px 14px rgba(0,0,0,0.4);
+}
+.leaflet-container a.leaflet-popup-close-button {
+    position: absolute;
+    top: 0;
+    right: 0;
+    border: none;
+    text-align: center;
+    width: 24px;
+    height: 24px;
+    font: 16px/24px Tahoma, Verdana, sans-serif;
+    color: #757575;
+    text-decoration: none;
+    background: transparent;
+}
+.leaflet-container a.leaflet-popup-close-button:hover,
+.leaflet-container a.leaflet-popup-close-button:focus {
+    color: #585858;
+}
+.leaflet-popup-scrolled {
+    overflow: auto;
+}
+
+.leaflet-oldie .leaflet-popup-content-wrapper {
+    -ms-zoom: 1;
+}
+.leaflet-oldie .leaflet-popup-tip {
+    width: 24px;
+    margin: 0 auto;
+
+    -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
+    filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
+}
+
+.leaflet-oldie .leaflet-control-zoom,
+.leaflet-oldie .leaflet-control-layers,
+.leaflet-oldie .leaflet-popup-content-wrapper,
+.leaflet-oldie .leaflet-popup-tip {
+    border: 1px solid #999;
+}
+
+
+/* div icon */
+
+.leaflet-div-icon {
+    background: #fff;
+    border: 1px solid #666;
+}
+
+
+/* Tooltip */
+/* Base styles for the element that has a tooltip */
+.leaflet-tooltip {
+    position: absolute;
+    padding: 6px;
+    background-color: #fff;
+    border: 1px solid #fff;
+    border-radius: 3px;
+    color: #222;
+    white-space: nowrap;
+    -webkit-user-select: none;
+    -moz-user-select: none;
+    -ms-user-select: none;
+    user-select: none;
+    pointer-events: none;
+    box-shadow: 0 1px 3px rgba(0,0,0,0.4);
+}
+.leaflet-tooltip.leaflet-interactive {
+    cursor: pointer;
+    pointer-events: auto;
+}
+.leaflet-tooltip-top:before,
+.leaflet-tooltip-bottom:before,
+.leaflet-tooltip-left:before,
+.leaflet-tooltip-right:before {
+    position: absolute;
+    pointer-events: none;
+    border: 6px solid transparent;
+    background: transparent;
+    content: "";
+}
+
+/* Directions */
+
+.leaflet-tooltip-bottom {
+    margin-top: 6px;
+}
+.leaflet-tooltip-top {
+    margin-top: -6px;
+}
+.leaflet-tooltip-bottom:before,
+.leaflet-tooltip-top:before {
+    left: 50%;
+    margin-left: -6px;
+}
+.leaflet-tooltip-top:before {
+    bottom: 0;
+    margin-bottom: -12px;
+    border-top-color: #fff;
+}
+.leaflet-tooltip-bottom:before {
+    top: 0;
+    margin-top: -12px;
+    margin-left: -6px;
+    border-bottom-color: #fff;
+}
+.leaflet-tooltip-left {
+    margin-left: -6px;
+}
+.leaflet-tooltip-right {
+    margin-left: 6px;
+}
+.leaflet-tooltip-left:before,
+.leaflet-tooltip-right:before {
+    top: 50%;
+    margin-top: -6px;
+}
+.leaflet-tooltip-left:before {
+    right: 0;
+    margin-right: -12px;
+    border-left-color: #fff;
+}
+.leaflet-tooltip-right:before {
+    left: 0;
+    margin-left: -12px;
+    border-right-color: #fff;
+}
+
+/* Printing */
+
+@media print {
+    /* Prevent printers from removing background-images of controls. */
+    .leaflet-control {
+        -webkit-print-color-adjust: exact;
+        print-color-adjust: exact;
+    }
+}

File diff suppressed because it is too large
+ 4 - 0
agent/leaflet/leaflet.js


+ 77 - 0
agent/logic/chatbot_api_dev.php

@@ -0,0 +1,77 @@
+<?php
+	
+	
+	//
+	// !list
+	//
+	$dictResponse = array();
+	$dictResponse["success"] = false;
+	$dictResponse["status_code"] = 400;
+	$dictResponse["status_description"] = "parameter missing";
+	
+	//
+	// main - no interface
+	//
+	if ( !isset($_REQUEST["interface"]) || $_REQUEST["interface"] == "" ) {
+	
+		//
+		$dictResponse = array();
+		$dictResponse["success"] = true;
+		$dictResponse["status_code"] = 200;
+		$dictResponse["status_description"] = "ok";
+	
+		$data = "<b>Hello world!</b><br>
+		weather information ;-)<br>
+		<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" fill=\"currentColor\" class=\"bi bi-cloud-drizzle\" viewBox=\"0 0 16 16\">
+		  <path d=\"M4.158 12.025a.5.5 0 0 1 .316.633l-.5 1.5a.5.5 0 0 1-.948-.316l.5-1.5a.5.5 0 0 1 .632-.317m6 0a.5.5 0 0 1 .316.633l-.5 1.5a.5.5 0 0 1-.948-.316l.5-1.5a.5.5 0 0 1 .632-.317m-3.5 1.5a.5.5 0 0 1 .316.633l-.5 1.5a.5.5 0 0 1-.948-.316l.5-1.5a.5.5 0 0 1 .632-.317m6 0a.5.5 0 0 1 .316.633l-.5 1.5a.5.5 0 1 1-.948-.316l.5-1.5a.5.5 0 0 1 .632-.317m.747-8.498a5.001 5.001 0 0 0-9.499-1.004A3.5 3.5 0 1 0 3.5 11H13a3 3 0 0 0 .405-5.973M8.5 2a4 4 0 0 1 3.976 3.555.5.5 0 0 0 .5.445H13a2 2 0 0 1 0 4H3.5a2.5 2.5 0 1 1 .605-4.926.5.5 0 0 0 .596-.329A4 4 0 0 1 8.5 2\"/>
+		</svg>
+		";
+	
+		$dictResponse["response"] = "".$data;
+	
+	}
+	
+	//
+	// interface: dev
+	//
+	if ( isset($_REQUEST["interface"]) && $_REQUEST["interface"] == "dev" ) {
+		//echo "<pre>"; print_r($_REQUEST); echo "</pre>";
+	
+		//
+		$dictResponse = array();
+		$dictResponse["success"] = true;
+		$dictResponse["status_code"] = 200;
+		$dictResponse["status_description"] = "ok";
+	
+		//
+		$arrEntries = array();
+		
+		$dictEntry = array();
+		$dictEntry["id"] = "1";
+		$dictEntry["name"] = "name 1";
+		$arrEntries[] = $dictEntry;
+		
+		$dictEntry = array();
+		$dictEntry["id"] = "2";
+		$dictEntry["name"] = "name 2";
+		$arrEntries[] = $dictEntry;
+		
+		//
+		$dictResponse["entries"] = $arrEntries;
+	}
+	
+	
+	
+	
+	
+	// JSON_HEX_APOS ???
+	$output = json_encode($dictResponse, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+	
+	//
+	header('Content-Type: application/json');
+	http_response_code($dictResponse["status_code"]);
+	echo $output;
+	
+	//
+	exit;
+?>

+ 414 - 0
agent/logic/map_awigo.php

@@ -0,0 +1,414 @@
+<?php
+
+// get data from AWIGO API
+$ch = curl_init('https://www.awigo.de/index.php?id=122');
+curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+curl_setopt($ch, CURLOPT_POST, true);
+curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
+    'legacy_eID' => 'awigoLocations',
+    'awigo_locations[method]' => 'getLocations',
+    'awigo_locations[lat]' => 52.28,
+    'awigo_locations[lon]' => 8.05,
+    'awigo_locations[v]' => 2,
+    'awigo_locations[format]' => 'json'
+]));
+$response = curl_exec($ch);
+curl_close($ch);
+$orte = json_decode($response, true);
+$orteJsArray = json_encode($orte, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+
+// ### HTML-Response
+$html = '
+<!DOCTYPE html>
+<html lang="de">
+<head>
+<link rel="stylesheet" href="https://area51.cybob.com/praktikum/daniel/agent/leaflet/leaflet.css">
+<link rel="stylesheet" href="https://area51.cybob.com/praktikum/daniel/agent/css/map.css">
+<script src="https://area51.cybob.com/praktikum/daniel/agent/leaflet/leaflet.js"></script>
+</head>
+<body>
+<div id="map-wrapper">
+    <div id="zipcode-search-wrapper">
+        <div class="input-with-clear">
+              <input
+                type="input"
+                id="zipcode-input"
+                placeholder="PLZ"
+                inputmode="numeric"
+                list="zipcode-datalist"
+                maxlength="5"
+                autocomplete="true"
+              />
+              <button id="zipcode-search-close" type="button" aria-label="Eingabe löschen">
+                <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+                  <line x1="1" y1="1" x2="15" y2="15" stroke="currentColor" stroke-width="2"/>
+                  <line x1="15" y1="1" x2="1" y2="15" stroke="currentColor" stroke-width="2"/>
+                </svg>
+              </button>
+        </div>
+      <button id="zipcode-search-button" type="button">🔍</button>
+    </div>
+    <datalist id="zipcode-datalist"></datalist>
+    
+    <div id="map-info">
+        <button id="map-info-close" aria-label="Schließen" title="Schließen">
+
+        <img src="https://area51.cybob.com/praktikum/daniel/agent/images/x.svg" alt="Schließen"/>
+        </button>
+        <a id="map-link" href="#" target="_blank" title="In Google Maps öffnen" style="display:inline-flex;align-items:center;gap:4px;">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" class="input-close">
+            <path d="M4.146 8.354a.5.5 0 0 1 0-.708L9.793 2H6.5a.5.5 0 0 1 0-1h4a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V2.707L4.854 8.854a.5.5 0 0 1-.708 0z"/>
+            <path d="M13.5 14a.5.5 0 0 1-.5.5H3a2 2 0 0 1-2-2V3.5a.5.5 0 0 1 1 0V12.5a1 1 0 0 0 1 1h10a.5.5 0 0 1 .5.5z"/>
+          </svg>
+          <span>Google Maps</span>
+        </a>
+    
+        <h4 id="map-info-title">Bitte einen Ort auswählen!</h4>
+        <span id="map-info-street"></span><br>
+        <span id="map-info-plz"></span><br>
+        <span><a id="map-info-telephone"></a></span><br>
+        <br>
+           
+        <div id="map-info-opening-hours-weekdays"></div>   
+        <div id="map-info-description"></div>
+    </div>
+  <div id="map"></div>
+</div>
+<script>
+
+// ### Default is in Osnabrueck ###
+  const defaultView = {
+    lat: 52.2799,
+    lng: 8.0472,
+    zoom: 9,
+    zoomClose: 16
+  };
+  
+  // ### Default Offset for compact view ###
+  const LAT_OFFSET = 0.0011;
+  const MIN_DEVIATION = 0.00003;
+  const COMPACT_THRESHOLD = 500; // px
+  
+  // ### State Memory ###
+  const mapState = {
+   isZoomed: false,
+   isDragging: false,
+   isCompact: false
+   };
+   
+   // ### Leaflet Map Object ###
+  let map = null;
+  
+  // ### Markers with queried json info (ort) ###
+  let markers  = [];
+  
+  function hideMarkers(markers) {
+      markers.forEach(marker => {
+            const markerElement = marker.getElement();
+            const shadow = marker._shadow;
+            if (markerElement) {
+                markerElement.style.display = "none";
+            }
+            if (shadow) {
+                shadow.style.display = "none"
+            }
+      })
+  }
+  
+  function hideMarkerShadows(markers) {
+      markers.forEach(marker => {
+           const shadow = marker._shadow;
+           if (shadow) shadow.style.display = "none";
+      })
+  }
+  
+  function showMarkers(markers) {
+      markers.forEach(marker => {
+          const markerElement = marker.getElement();
+            if (markerElement) {
+                markerElement.style.display = "";
+            }
+      })
+  }
+  
+  // Close InfoBox with small animation
+  function closeInfoBox() {
+      const infoBox = document.getElementById("map-info");
+      if (!infoBox.classList.contains("active")) {
+          return;
+      }
+      infoBox.classList.remove("active");
+      infoBox.classList.add("closing");
+      setTimeout(() => {
+          infoBox.classList.remove("closing");
+      }, 125);
+  }
+  
+  // check for user position => add marker and center map
+  function checkUserPosition() {
+      if (navigator.geolocation) {
+          navigator.geolocation.getCurrentPosition(function(position) {
+              setTimeout(() => {
+                  userLat = position.coords.latitude;
+                  userLng = position.coords.longitude;
+
+                  map.setView([userLat, userLng], 10);
+                  map.invalidateSize();
+              }, 500)
+          }, function(error) {
+              console.warn("Geolocation not found");
+          });
+      } else {
+          console.warn("Geolocation not supported by this browser.");
+      }
+  }
+
+  function loadLeafletScript(callback) {
+      var script = document.createElement("script");
+      script.src = "https://area51.cybob.com/praktikum/daniel/agent/leaflet/leaflet.js";
+      script.onload = callback;
+      document.head.appendChild(script);
+      console.log("Leaflet script loaded");  
+  }
+
+  // Fly to LatLng Location with optional offset for compact View
+  // Map Marker will then be centered, as the info box with appear from the bottom  
+  function flyToWithOptionalOffset(latLng, zoom) {
+      let newLat = latLng.lat;
+      if (mapState.isCompact) {
+        newLat -= LAT_OFFSET; // adjust latitude for compact view
+      }      
+      map.flyTo({lat: newLat, lng: latLng.lng}, zoom, {
+      animate: true,
+      duration: 0.5
+      });
+  }
+  
+  function getVisibleMapCenter() {
+      const center = map.getCenter().clone();
+      if (mapState.isCompact) {
+        center.lat -= LAT_OFFSET;
+      }
+      return center;
+  }
+  
+  function getLogicalMapCenter() {
+    let center = map.getCenter().clone();
+    if (mapState.isCompact) {
+        center.lat += LAT_OFFSET;    
+    }
+    return center;
+  }
+  
+  function updateInfoBox(ort) {
+        // replace info box strings
+        document.getElementById("map-info-title").innerHTML = ort.title;
+        document.getElementById("map-info-description").innerHTML = ort.description || "";
+        document.getElementById("map-info-opening-hours-weekdays").innerHTML = ort.openingTimes;
+        document.getElementById("map-info-street").innerHTML = ort.address || "";
+        document.getElementById("map-info-plz").innerHTML = (ort.zip + " " + ort.city) || "";
+        const tel = ort.telephone.replaceAll(" ", "")
+            .replaceAll(")", "")
+            .replaceAll("(", "")
+            .replaceAll("-", "");
+        document.getElementById("map-info-telephone").innerHTML = ("Tel.: " + ort.telephone);
+        document.getElementById("map-info-telephone").href = "tel:" + tel;
+    
+        // build & assign google maps url
+        const lat = ort.lat;
+        const lng = ort.lng;
+        document.getElementById("map-link").href = `https://www.google.com/maps?q=${lat},${lng}`;
+  }
+  
+  function showInfoBox() {
+        document.getElementById("map-info").classList.add("active");
+  }
+  
+  function isMapDeviationReached(marker) {
+      const logicalCenter = getLogicalMapCenter();
+      const deltaLat = Math.abs(logicalCenter.lat - marker.getLatLng().lat);
+      const deltaLng = Math.abs(logicalCenter.lng - marker.getLatLng().lng);
+      return deltaLat >= MIN_DEVIATION || deltaLng >= MIN_DEVIATION;
+  }
+  
+  function initializeMap() {
+      
+      // initialize the map
+      map = L.map("map", {
+          zoomAnimation: true,
+          markerZoomAnimation: true,
+          fadeAnimation: true,
+          attributionControl: true,
+      }).setView([52.2799, 8.0472], defaultView.zoom);
+      L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
+          attribution: "&copy; <a href=\\"https://www.openstreetmap.org/copyright\\">OpenStreetMap</a>"
+      }).addTo(map);
+      
+      // Checks with each resize if the map-wrapper is smaller than threshold
+      function updateMapLayoutResponsiveness() {
+          const mapWrapper = document.getElementById("map-wrapper");
+          const mapDiv = document.getElementById("map");
+          
+          const isCompact = mapWrapper.offsetWidth < COMPACT_THRESHOLD;
+          if (isCompact) {
+              mapWrapper.classList.add("compact");
+              mapDiv.classList.add("compact");
+              map.invalidateSize();
+              mapState.isCompact = true;
+          } else {
+              mapWrapper.classList.remove("compact");
+              mapDiv.classList.remove("compact");
+              mapState.isCompact = false;
+          }
+      }
+      updateMapLayoutResponsiveness();
+      window.addEventListener("resize", updateMapLayoutResponsiveness);
+
+      document.getElementById("map").addEventListener("mousedown", () => {
+          mapState.isDragging = false;
+      })
+
+      document.getElementById("map").addEventListener("mousemove", () => {
+          mapState.isDragging = true;
+      })
+
+      document.getElementById("map").addEventListener("click", (e) => {
+          if (
+              e.target.classList.contains("map-info") ||
+              e.target.classList.contains("leaflet-marker-icon") ||
+              e.target.classList.contains("leaflet-marker-icon") ||
+             mapState.isDragging 
+          ) {
+              return;
+          }
+          closeInfoBox();
+      })
+
+      document.getElementById("map-info-close").addEventListener("click", () => {
+          closeInfoBox();
+      });
+
+      // standard-icon (blue)
+      L.Icon.Default.mergeOptions({
+          iconRetinaUrl: "awigo-icon-gruenplatz-map.png",
+          iconUrl: "awigo-icon-gruenplatz-map.png",
+          shadowUrl: "marker-icon-shadow-default.png",
+          className: "awigo-marker-icon"
+      });
+      
+      // add marker from json-array
+      orte.forEach(function(ort) {
+          // remove shadows for AWIGO
+          var icon = new L.Icon.Default();
+          const marker = L.marker([ort.lat, ort.lng], {icon: icon}).addTo(map);
+          marker.ort = ort;
+          markers.push(marker);
+          
+          // Event-Listener for click on marker
+          marker.on("click", function() {
+              updateInfoBox(ort);
+              if (isMapDeviationReached(marker)) {
+                  showInfoBox();
+                  flyToWithOptionalOffset(marker.getLatLng(), defaultView.zoomClose);
+                  mapState.isZoomed = true;
+              }   else if (mapState.isZoomed){
+                  flyToWithOptionalOffset({lat: defaultView.lat, lng: defaultView.lng}, defaultView.zoom)
+                  closeInfoBox();
+                  mapState.isZoomed = false;
+              }
+          });
+      });
+
+      // re-render map (prevents missing tile issues)
+      map.invalidateSize();
+//      checkUserPosition();
+      hideMarkerShadows(markers);
+  }
+
+  // ### Program start ###
+  // filter for green places from AWIGO API
+  let orte = [];
+  orte =' . $orteJsArray . '["Locations"];
+  if (orte) {
+      orte = orte.filter((ort) => {
+          return ort.category.toLowerCase().includes("grün");
+      })
+  } else {
+      console.error("No locations found in the response from AWIGO API.");
+  }
+  
+  
+  let zipcodes = [...new Set(orte.map(o => o.zip))].sort();
+  
+  // Input Select
+  const input = document.getElementById("zipcode-input");
+  const datalist = document.getElementById("zipcode-datalist");
+    
+  // add available zipcodes to datalist
+  zipcodes.forEach(zip => {
+      const option = document.createElement("option");
+      option.value = zip;
+      datalist.appendChild(option);
+  });
+    
+  function searchByZip(zip) {
+      if (!zip || zip.length !== 5 || !/^\d{5}$/.test(zip)) {
+          showMarkers(markers);
+          return;
+      }
+        
+      const filteredMarkers = markers.filter(marker => marker.ort.zip === zip);
+      if (filteredMarkers.length > 0) {
+          const firstMarker = filteredMarkers[0];
+          
+          if (isMapDeviationReached(firstMarker)) {
+              flyToWithOptionalOffset(firstMarker.getLatLng(), defaultView.zoomClose);
+              mapState.isZoomed = true;
+              updateInfoBox(firstMarker.ort);
+              showInfoBox();
+          }
+          
+          filteredMarkers.forEach((marker) => {
+              hideMarkers(markers);
+              showMarkers(filteredMarkers);
+          })
+      }
+  }
+    
+  input.addEventListener("input", () => {
+      input.value = input.value.replace(/\D/g, "").slice(0,5);
+      if (input.value.length === 5) {
+          searchByZip(input.value);
+      } else {
+          showMarkers(markers);
+      }
+  });
+    
+  document.getElementById("zipcode-search-button").addEventListener("click", () => {
+      const zip = input.value.trim();
+      searchByZip(zip);
+  });
+  
+  document.getElementById("zipcode-search-close").addEventListener("click", () => {
+      input.value = "";
+      showMarkers(markers);
+  })
+  
+  // ### callback 
+  loadLeafletScript(initializeMap);
+</script>
+';
+
+// JSON-Response aufbauen
+$dictResponse = [
+    "success" => true,
+    "status_code" => 200,
+    "status_description" => "ok",
+    "response" => $html
+];
+
+$output = json_encode($dictResponse, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+header('Content-Type: application/json');
+http_response_code($dictResponse["status_code"]);
+echo $output;
+exit;

+ 399 - 0
agent/logic/map_template.php

@@ -0,0 +1,399 @@
+<?php
+
+// prepared data for testing (reduces workload on awigo API :D )
+$jsonData = file_get_contents('http://area51.cybob.com/praktikum/daniel/agent/data/map_template_data.json');
+$orte = json_decode($jsonData, true);
+$orteJsArray = json_encode($orte, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
+
+// ### HTML-Response
+$html = '
+<!DOCTYPE html>
+<html lang="de">
+<head>
+<link rel="stylesheet" href="https://area51.cybob.com/praktikum/daniel/agent/leaflet/leaflet.css">
+<link rel="stylesheet" href="https://area51.cybob.com/praktikum/daniel/agent/css/map.css">
+<script src="https://area51.cybob.com/praktikum/daniel/agent/leaflet/leaflet.js"></script>
+<style>
+.leaflet-marker-icon {
+    width: 25px !important;
+}
+</style>
+</head>
+<body>
+<div id="map-wrapper">
+    <div id="zipcode-search-wrapper">
+        <div class="input-with-clear">
+              <input
+                type="input"
+                id="zipcode-input"
+                placeholder="PLZ"
+                inputmode="numeric"
+                list="zipcode-datalist"
+                maxlength="5"
+                autocomplete="true"
+              />
+              <button id="zipcode-search-close" type="button" aria-label="Eingabe löschen">
+                <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+                  <line x1="1" y1="1" x2="15" y2="15" stroke="currentColor" stroke-width="2"/>
+                  <line x1="15" y1="1" x2="1" y2="15" stroke="currentColor" stroke-width="2"/>
+                </svg>
+              </button>
+        </div>
+      <button id="zipcode-search-button" type="button">🔍</button>
+    </div>
+    <datalist id="zipcode-datalist"></datalist>
+    
+    <div id="map-info">
+        <button id="map-info-close" aria-label="Schließen" title="Schließen">
+
+        <img src="https://area51.cybob.com/praktikum/daniel/agent/images/x.svg" alt="Schließen"/>
+        </button>
+        <a id="map-link" href="#" target="_blank" title="In Google Maps öffnen" style="display:inline-flex;align-items:center;gap:4px;">
+          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16" class="input-close">
+            <path d="M4.146 8.354a.5.5 0 0 1 0-.708L9.793 2H6.5a.5.5 0 0 1 0-1h4a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V2.707L4.854 8.854a.5.5 0 0 1-.708 0z"/>
+            <path d="M13.5 14a.5.5 0 0 1-.5.5H3a2 2 0 0 1-2-2V3.5a.5.5 0 0 1 1 0V12.5a1 1 0 0 0 1 1h10a.5.5 0 0 1 .5.5z"/>
+          </svg>
+          <span>Google Maps</span>
+        </a>
+    
+        <h4 id="map-info-title">Bitte einen Ort auswählen!</h4>
+        <span id="map-info-street"></span><br>
+        <span id="map-info-plz"></span><br>
+        <span><a id="map-info-telephone"></a></span><br>
+        <br>
+           
+        <div id="map-info-opening-hours-weekdays"></div>   
+        <div id="map-info-description"></div>
+    </div>
+  <div id="map"></div>
+</div>
+<script>
+
+// ### Default is in Osnabrueck ###
+  const defaultView = {
+    lat: 52.2799,
+    lng: 8.0472,
+    zoom: 9,
+    zoomClose: 16
+  };
+  
+  // ### Default Offset for compact view ###
+  const LAT_OFFSET = 0.0011;
+  const MIN_DEVIATION = 0.00003;
+  const COMPACT_THRESHOLD = 500; // px
+  
+  // ### State Memory ###
+  const mapState = {
+   isZoomed: false,
+   isDragging: false,
+   isCompact: false
+   };
+   
+   // ### Leaflet Map Object ###
+  let map = null;
+  
+  // ### Markers with queried json info (ort) ###
+  let markers  = [];
+  
+  function hideMarkers(markers) {
+      markers.forEach(marker => {
+            const markerElement = marker.getElement();
+            const shadow = marker._shadow;
+            if (markerElement) {
+                markerElement.style.display = "none";
+            }
+            if (shadow) {
+                shadow.style.display = "none"
+            }
+      })
+  }
+  
+  function hideMarkerShadows(markers) {
+      markers.forEach(marker => {
+           const shadow = marker._shadow;
+           if (shadow) shadow.style.display = "none";
+      })
+  }
+  
+  function showMarkers(markers) {
+      markers.forEach(marker => {
+          const markerElement = marker.getElement();
+            if (markerElement) {
+                markerElement.style.display = "";
+            }
+      })
+  }
+  
+  // Close InfoBox with small animation
+  function closeInfoBox() {
+      const infoBox = document.getElementById("map-info");
+      if (!infoBox.classList.contains("active")) {
+          return;
+      }
+      infoBox.classList.remove("active");
+      infoBox.classList.add("closing");
+      setTimeout(() => {
+          infoBox.classList.remove("closing");
+      }, 125);
+  }
+  
+  // check for user position => add marker and center map
+  function checkUserPosition() {
+      if (navigator.geolocation) {
+          navigator.geolocation.getCurrentPosition(function(position) {
+              setTimeout(() => {
+                  userLat = position.coords.latitude;
+                  userLng = position.coords.longitude;
+
+                  map.setView([userLat, userLng], 10);
+                  map.invalidateSize();
+              }, 500)
+          }, function(error) {
+              console.warn("Geolocation not found");
+          });
+      } else {
+          console.warn("Geolocation not supported by this browser.");
+      }
+  }
+
+  function loadLeafletScript(callback) {
+      var script = document.createElement("script");
+      script.src = "https://area51.cybob.com/praktikum/daniel/agent/leaflet/leaflet.js";
+      script.onload = callback;
+      document.head.appendChild(script);
+      console.log("Leaflet script loaded");  
+  }
+
+  // Fly to LatLng Location with optional offset for compact View
+  // Map Marker will then be centered, as the info box with appear from the bottom  
+  function flyToWithOptionalOffset(latLng, zoom) {
+      let newLat = latLng.lat;
+      if (mapState.isCompact) {
+        newLat -= LAT_OFFSET; // adjust latitude for compact view
+      }      
+      map.flyTo({lat: newLat, lng: latLng.lng}, zoom, {
+      animate: true,
+      duration: 0.5
+      });
+  }
+  
+  function getVisibleMapCenter() {
+      const center = map.getCenter().clone();
+      if (mapState.isCompact) {
+        center.lat -= LAT_OFFSET;
+      }
+      return center;
+  }
+  
+  function getLogicalMapCenter() {
+    let center = map.getCenter().clone();
+    if (mapState.isCompact) {
+        center.lat += LAT_OFFSET;    
+    }
+    return center;
+  }
+  
+  function updateInfoBox(ort) {
+        // replace info box strings
+        document.getElementById("map-info-title").innerHTML = ort.title;
+        document.getElementById("map-info-description").innerHTML = ort.description || "";
+        document.getElementById("map-info-opening-hours-weekdays").innerHTML = ort.openingTimes;
+        document.getElementById("map-info-street").innerHTML = ort.address || "";
+        document.getElementById("map-info-plz").innerHTML = (ort.zip + " " + ort.city) || "";
+        const tel = ort.telephone.replaceAll(" ", "")
+            .replaceAll(")", "")
+            .replaceAll("(", "")
+            .replaceAll("-", "");
+        document.getElementById("map-info-telephone").innerHTML = ("Tel.: " + ort.telephone);
+        document.getElementById("map-info-telephone").href = "tel:" + tel;
+    
+        // build & assign google maps url
+        const lat = ort.lat;
+        const lng = ort.lng;
+        document.getElementById("map-link").href = `https://www.google.com/maps?q=${lat},${lng}`;
+  }
+  
+  function showInfoBox() {
+        document.getElementById("map-info").classList.add("active");
+  }
+  
+  function isMapDeviationReached(marker) {
+      const logicalCenter = getLogicalMapCenter();
+      const deltaLat = Math.abs(logicalCenter.lat - marker.getLatLng().lat);
+      const deltaLng = Math.abs(logicalCenter.lng - marker.getLatLng().lng);
+      return deltaLat >= MIN_DEVIATION || deltaLng >= MIN_DEVIATION;
+  }
+  
+  function initializeMap() {
+      
+      // initialize the map
+      map = L.map("map", {
+          zoomAnimation: true,
+          markerZoomAnimation: true,
+          fadeAnimation: true,
+          attributionControl: true,
+      }).setView([52.2799, 8.0472], defaultView.zoom);
+      L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
+          attribution: "&copy; <a href=\\"https://www.openstreetmap.org/copyright\\">OpenStreetMap</a>"
+      }).addTo(map);
+      
+      // Checks with each resize if the map-wrapper is smaller than threshold
+      function updateMapLayoutResponsiveness() {
+          const mapWrapper = document.getElementById("map-wrapper");
+          const mapDiv = document.getElementById("map");
+          
+          const isCompact = mapWrapper.offsetWidth < COMPACT_THRESHOLD;
+          if (isCompact) {
+              mapWrapper.classList.add("compact");
+              mapDiv.classList.add("compact");
+              map.invalidateSize();
+              mapState.isCompact = true;
+          } else {
+              mapWrapper.classList.remove("compact");
+              mapDiv.classList.remove("compact");
+              mapState.isCompact = false;
+          }
+      }
+      updateMapLayoutResponsiveness();
+      window.addEventListener("resize", updateMapLayoutResponsiveness);
+
+      document.getElementById("map").addEventListener("mousedown", () => {
+          mapState.isDragging = false;
+      })
+
+      document.getElementById("map").addEventListener("mousemove", () => {
+          mapState.isDragging = true;
+      })
+
+      document.getElementById("map").addEventListener("click", (e) => {
+          if (
+              e.target.classList.contains("map-info") ||
+              e.target.classList.contains("leaflet-marker-icon") ||
+              e.target.classList.contains("leaflet-marker-icon") ||
+             mapState.isDragging 
+          ) {
+              return;
+          }
+          closeInfoBox();
+      })
+
+      document.getElementById("map-info-close").addEventListener("click", () => {
+          closeInfoBox();
+      });
+
+      // standard-icon (blue)
+      L.Icon.Default.mergeOptions({
+          iconRetinaUrl: "marker-icon-default-green.png",
+          iconUrl: "marker-icon-default-green.png",
+          shadowUrl: "marker-icon-shadow-default.png",
+          className: "awigo-marker-icon"
+      });
+      
+      // add marker from json-array
+      orte.forEach(function(ort) {
+          // remove shadows for AWIGO
+          var icon = new L.Icon.Default();
+          const marker = L.marker([ort.lat, ort.lng], {icon: icon}).addTo(map);
+          marker.ort = ort;
+          markers.push(marker);
+          
+          // Event-Listener for click on marker
+          marker.on("click", function() {
+              updateInfoBox(ort);
+              if (isMapDeviationReached(marker)) {
+                  showInfoBox();
+                  flyToWithOptionalOffset(marker.getLatLng(), defaultView.zoomClose);
+                  mapState.isZoomed = true;
+              }   else if (mapState.isZoomed){
+                  flyToWithOptionalOffset({lat: defaultView.lat, lng: defaultView.lng}, defaultView.zoom)
+                  closeInfoBox();
+                  mapState.isZoomed = false;
+              }
+          });
+      });
+
+      // re-render map (prevents missing tile issues)
+      map.invalidateSize();
+//      checkUserPosition();
+      hideMarkerShadows(markers);
+  }
+
+  // ### Program start ###
+  // filter for green places from AWIGO API
+  let orte =' . $orteJsArray . ';
+  
+  
+  let zipcodes = [...new Set(orte.map(o => o.zip))].sort();
+  
+  // Input Select
+  const input = document.getElementById("zipcode-input");
+  const datalist = document.getElementById("zipcode-datalist");
+    
+  // add available zipcodes to datalist
+  zipcodes.forEach(zip => {
+      const option = document.createElement("option");
+      option.value = zip;
+      datalist.appendChild(option);
+  });
+    
+  function searchByZip(zip) {
+      if (!zip || zip.length !== 5 || !/^\d{5}$/.test(zip)) {
+          showMarkers(markers);
+          return;
+      }
+        
+      const filteredMarkers = markers.filter(marker => marker.ort.zip === zip);
+      if (filteredMarkers.length > 0) {
+          const firstMarker = filteredMarkers[0];
+          
+          if (isMapDeviationReached(firstMarker)) {
+              flyToWithOptionalOffset(firstMarker.getLatLng(), defaultView.zoomClose);
+              mapState.isZoomed = true;
+              updateInfoBox(firstMarker.ort);
+              showInfoBox();
+          }
+          
+          filteredMarkers.forEach((marker) => {
+              hideMarkers(markers);
+              showMarkers(filteredMarkers);
+          })
+      }
+  }
+    
+  input.addEventListener("input", () => {
+      input.value = input.value.replace(/\D/g, "").slice(0,5);
+      if (input.value.length === 5) {
+          searchByZip(input.value);
+      } else {
+          showMarkers(markers);
+      }
+  });
+    
+  document.getElementById("zipcode-search-button").addEventListener("click", () => {
+      const zip = input.value.trim();
+      searchByZip(zip);
+  });
+  
+  document.getElementById("zipcode-search-close").addEventListener("click", () => {
+      input.value = "";
+      showMarkers(markers);
+  })
+  
+  // ### callback 
+  loadLeafletScript(initializeMap);
+</script>
+';
+
+// JSON-Response aufbauen
+$dictResponse = [
+    "success" => true,
+    "status_code" => 200,
+    "status_description" => "ok",
+    "response" => $html
+];
+
+$output = json_encode($dictResponse, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
+header('Content-Type: application/json');
+http_response_code($dictResponse["status_code"]);
+echo $output;
+exit;

+ 66 - 0
webhook/whatsapp.php

@@ -0,0 +1,66 @@
+<?php
+//// Facebook verification
+//if ($_SERVER['REQUEST_METHOD'] === 'GET') {
+//    $verify_token = 'EAAJB2BlGxRMBO62lsPzKpIZA0ZAojmgqubtBdTX6WJpFoDeISffzlcObVG1y0o9J2sGcYPOPfaG0mhSGKHSDFY67njunqRQIL3YMsNE8xC5NDQ2TgKdjfib6tjhfVT0Tg8XMKt7yvN9NP3GQek3i4oB3AXl5dX5bBuFfhZB0YbHgXJbNohMXHZCHRBCSw1PmsECK3j3BjxZCoRiV7PGwuoKFCFc3M5UiRjiUqaZBsT';
+//    if ($_GET['hub_verify_token'] === $verify_token) {
+//        echo $_GET['hub_challenge'];
+//        exit;
+//    } else {
+//        http_response_code(403);
+//        exit;
+//    }
+//}
+//
+//
+//// Receive Message from WhatsApp
+//if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+//    $input = json_decode(file_get_contents('php://input'), true);
+//    $message = $input['entry'][0]['changes'][0]['value']['messages'][0]['text']['body'];
+//    $from = $input['entry'][0]['changes'][0]['value']['messages'][0]['from'];
+//
+//    $token = 'EAAJB2BlGxRMBOZBj1DZArQo02p6hqxEluzCzbSvNvlNs7fBtb2tv57KZAflNUte6j9ip37ZCXmUVXZBZCtp44Sb93Gb83dh2ZAEBZA3ZCbzAlwl47CPtjkWi0pEIE5f1Fi1hkZCWcyXs6ZBQVzZBicCcOZBNStGMHOCwQZCmLqMT5U9Juv7L0eQbvTUB6clwtFnvZBmJA2ZASgPymjBC04odbwCiP5TSzNZBG3iZAs6ULecUkZD';
+//    $phone_id = '706930449163857';
+//
+//    $url = "https://graph.facebook.com/v19.0/$phone_id/messages";
+//
+//    $msg_encoded = urlencode($message);
+//    $bot_url = "https://agent.cybob.com/awigo/?controller=message&message=$msg_encoded";
+//
+//    $ch2 = curl_init($bot_url);
+//    curl_setopt($ch2, CURLOPT_RETURNTRANSFER, true);
+//    curl_setopt($ch2, CURLOPT_ENCODING, '');
+//    curl_setopt($ch2, CURLOPT_FOLLOWLOCATION, true);
+//    $bot_response = curl_exec($ch2);
+//    $curl_error = curl_error($ch2);
+//    curl_close($ch2);
+//
+//    $bot_data = json_decode($bot_response, true);
+//
+//    if (isset($bot_data['reply'])) {
+//        $reply = $bot_data['reply'];
+//        $reply = strip_tags(html_entity_decode($reply));
+//    } else {
+//        $reply = "Botfehler:\nAntwort: $bot_response\nFehler: $curl_error";
+//    }
+//
+//    $payload = [
+//        "messaging_product" => "whatsapp",
+//        "to" => $from,
+//        "type" => "text",
+//        "text" => ["body" => $reply]
+//    ];
+//
+//    $ch = curl_init($url);
+//    curl_setopt($ch, CURLOPT_HTTPHEADER, [
+//        "Authorization: Bearer $token",
+//        "Content-Type: application/json"
+//    ]);
+//    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
+//    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+//    curl_exec($ch);
+//    curl_close($ch);
+//
+//    echo json_encode(["status" => "ok"]);
+//    exit;
+//}
+//?>

Some files were not shown because too many files changed in this diff