This is an automated email from the ASF dual-hosted git repository.
bchapuis pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-baremaps-site.git
The following commit(s) were added to refs/heads/main by this push:
new 2053e74 Add react components to the demo (#23)
2053e74 is described below
commit 2053e748d99c146c625514d5a0821584a958a22a
Author: Leonard <[email protected]>
AuthorDate: Thu May 4 12:47:58 2023 +0200
Add react components to the demo (#23)
* Add geocoder search component
* Add iptoloc
* Add new icon title component
* Add info on geocoder
* Update to https
* Remove unused code
---
src/components/GeocoderSearch/index.tsx | 130 +++++++++++++++++++++++
src/components/GeocoderSearch/style.module.css | 107 +++++++++++++++++++
src/components/map/index.tsx | 102 ++++++++++++------
src/components/titles/IconTitle/index.tsx | 21 ++++
src/components/titles/IconTitle/style.module.css | 7 ++
src/pages/_app.mdx | 2 +
src/pages/index.mdx | 36 +++++--
7 files changed, 366 insertions(+), 39 deletions(-)
diff --git a/src/components/GeocoderSearch/index.tsx
b/src/components/GeocoderSearch/index.tsx
new file mode 100644
index 0000000..547e525
--- /dev/null
+++ b/src/components/GeocoderSearch/index.tsx
@@ -0,0 +1,130 @@
+import { useCallback, useState } from 'react';
+import maplibregl from 'maplibre-gl';
+import cn from 'clsx';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faSearch } from '@fortawesome/free-solid-svg-icons';
+
+import styles from './style.module.css';
+
+type GeocoderResults = {
+ data: {
+ name: string;
+ latitude: number;
+ longitude: number;
+ } & Record<string, any>;
+}[];
+
+interface InputSearchProps {
+ url: string;
+ map?: maplibregl.Map;
+}
+
+export const GeocoderSearch: React.FC<InputSearchProps> = ({ url, map }) => {
+ const [search, setSearch] = useState('');
+ const [results, setResults] = useState<GeocoderResults>([]);
+ const [marker, setMarker] = useState<maplibregl.Marker | null>(null);
+ const [error, setError] = useState(false);
+
+ const onChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
+ setSearch(e.target.value);
+ if (e.target.value.length < 3) {
+ setResults([]);
+ setError(false);
+ return;
+ }
+ try {
+ const res = await fetch(
+ url + '?' + new URLSearchParams({ queryText: e.target.value }),
+ { method: 'GET' }
+ );
+ const data = await res.json();
+ setResults(data.results.slice(0, 10) as GeocoderResults);
+ setError(false);
+ } catch (err) {
+ setError(true);
+ }
+ };
+
+ const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
+ e.preventDefault();
+ if (results.length > 0) {
+ const first = results[0].data;
+ updateMap(first);
+ }
+ };
+
+ const updateMap = useCallback(
+ (result: GeocoderResults[0]['data']) => {
+ if (!map) {
+ return;
+ }
+ if (marker) {
+ marker.remove();
+ }
+ const newMarker = new maplibregl.Marker()
+ .setLngLat([result.longitude, result.latitude])
+ .addTo(map);
+ setMarker(newMarker);
+ map.panTo([result.longitude, result.latitude]);
+ setResults([]);
+ setSearch(result.name);
+ },
+ [map, marker]
+ );
+
+ return (
+ <div className={cn(styles['ctrl-container'], 'maplibregl-ctrl-top-left')}>
+ <div
+ className={cn(
+ styles['ctrl-group'],
+ 'maplibregl-ctrl maplibregl-ctrl-group'
+ )}
+ >
+ <FontAwesomeIcon icon={faSearch} className={styles['search-icon']} />
+ <form onSubmit={onSubmit} className={styles['search-form']}>
+ <input
+ className={cn(styles.search)}
+ type="text"
+ placeholder="Search"
+ value={search}
+ onChange={e => onChange(e)}
+ />
+ </form>
+ </div>
+ {(results.length > 0 || error) && (
+ <div
+ className={cn(
+ styles['ctrl-group-dropdown'],
+ 'maplibregl-ctrl maplibregl-ctrl-group'
+ )}
+ >
+ {error ? (
+ <div className={styles['search-error']}>
+ An error occured while searching...
+ </div>
+ ) : (
+ <>
+ <ul className={styles['search-results']}>
+ {results.map((result, index) => (
+ <li
+ key={index}
+ className={styles['search-result']}
+ onClick={() => updateMap(result.data)}
+ >
+ <div className={styles['search-result-name']}>
+ {result.data.name}
+ </div>
+ <div className={styles['search-result-country']}>
+ {result.data.countryCode}
+ </div>
+ </li>
+ ))}
+ </ul>
+ <small className={styles['attribution']}>© GeoNames</small>
+ </>
+ )}
+ </div>
+ )}
+ </div>
+ );
+};
diff --git a/src/components/GeocoderSearch/style.module.css
b/src/components/GeocoderSearch/style.module.css
new file mode 100644
index 0000000..a519823
--- /dev/null
+++ b/src/components/GeocoderSearch/style.module.css
@@ -0,0 +1,107 @@
+.ctrl-container {
+ width: 450px;
+ color: black;
+}
+
+/* Responsive width for small screens */
+@media screen and (max-width: 768px) {
+ .ctrl-container {
+ width: 85%;
+ }
+}
+
+.ctrl-group {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ transition: box-shadow 0.1s ease-in-out;
+}
+
+.ctrl-group:has(.search:focus-visible) {
+ box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.25);
+}
+
+.ctrl-group-dropdown {
+ overflow: hidden;
+ width: 100%;
+ transition: width 0.1s ease-in-out;
+}
+
+.search-form {
+ width: 100%;
+}
+
+.search {
+ /* Place the search at the top */
+ z-index: 1;
+ width: 100%;
+ height: 29px;
+ border: 0;
+ display: block;
+ outline: none;
+ padding: 0 10px;
+ background-color: transparent;
+}
+
+.search:focus-visible {
+ box-shadow: none;
+}
+
+/* Center vertically search text placeholder */
+.search::-webkit-input-placeholder {
+ line-height: 29px;
+}
+
+.search-icon {
+ color: #233333;
+ fill: #233333;
+ padding-left: 10px;
+}
+
+.search-results {
+ width: 100%;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ gap: 5px;
+}
+
+.search-result {
+ display: flex;
+ align-items: center;
+ padding: 0 10px;
+ transition: background-color 0.1s ease-in-out;
+}
+
+.search-result:first-of-type {
+ padding-top: 5px;
+}
+
+.search-result:hover {
+ background-color: #e6e6e6;
+}
+
+.search-result-country {
+ margin-left: auto;
+ padding: 2px 5px;
+ border-radius: 4px;
+ background-color: #e6e6e6;
+ font-size: 12px;
+ font-weight: 700;
+}
+
+.search-error {
+ width: 100%;
+ color: gray;
+ font-size: 12px;
+ padding: 5px 10px;
+}
+
+.attribution {
+ width: 100%;
+ display: block;
+ margin: 5px auto;
+ color: gray;
+ text-align: center;
+ font-size: 12px;
+}
diff --git a/src/components/map/index.tsx b/src/components/map/index.tsx
index 65d6421..07ef0b5 100644
--- a/src/components/map/index.tsx
+++ b/src/components/map/index.tsx
@@ -4,47 +4,85 @@ import MaplibreInspect from
'@/lib/maplibre/dist/maplibre-gl-inspect/maplibre-gl
import MaplibreTileBoundaries from
'@/lib/maplibre/dist/maplibre-gl-tile-boundaries/maplibre-gl-tile-boundaries';
import styles from './style.module.css';
+import { GeocoderSearch } from '../GeocoderSearch';
-export default function Map() {
+interface MapProps {
+ longitude?: number;
+ latitude?: number;
+ zoom?: number;
+ ipToLoc?: boolean;
+}
+
+export default function Map({
+ longitude = 6.625,
+ latitude = 46.51,
+ zoom = 14,
+ ipToLoc = true
+}: MapProps) {
const mapContainer = useRef(null);
- const map = useRef(null);
- const [lng] = useState(6.625);
- const [lat] = useState(46.51);
- const [zoom] = useState(14);
+ const [map, setMap] = useState(null);
+ // Initialize map when component mounts
useEffect(() => {
- if (map.current) return;
- map.current = new maplibregl.Map({
- container: mapContainer.current,
- style: `https://demo.baremaps.com/style.json`,
- center: [lng, lat],
- zoom: zoom
- });
- map.current.addControl(new maplibregl.NavigationControl({}));
-
- // Add the inspect control to the map.
- map.current.addControl(
- new MaplibreInspect({
- showMapPopup: true,
- showMapPopupOnHover: false,
- showInspectMapPopupOnHover: false,
- popup: new maplibregl.Popup({
- closeButton: true,
- closeOnClick: true
+ const initMap = async () => {
+ if (ipToLoc) {
+ try {
+ const res = await fetch('https://api.ipify.org/?format=json');
+ const { ip } = await res.json();
+ const res2 = await fetch(`https://demo.baremaps.com/api/ip/${ip}`);
+ const results = await res2.json();
+ if (results.length > 0) {
+ longitude = results[0].longitude;
+ latitude = results[0].latitude;
+ }
+ map.jumpTo({
+ center: [longitude, latitude],
+ zoom: zoom
+ });
+ } catch (err) {
+ // For the moment, we fallback to the default values
+ }
+ }
+ setMap(
+ new maplibregl.Map({
+ container: mapContainer.current,
+ style: `https://demo.baremaps.com/style.json`,
+ center: [longitude, latitude],
+ zoom: zoom
})
- })
- );
+ );
+ };
+ initMap();
+ }, []);
- // Add the tile boundaries control to the map.
- map.current.addControl(
- new MaplibreTileBoundaries({
- showBoundaries: false
- })
- );
- });
+ // Add controls to the map
+ useEffect(() => {
+ if (map) {
+ map.addControl(new maplibregl.NavigationControl({}));
+ // Add the inspect control to the map.
+ map.addControl(
+ new MaplibreInspect({
+ showMapPopup: true,
+ showMapPopupOnHover: false,
+ showInspectMapPopupOnHover: false,
+ popup: new maplibregl.Popup({
+ closeButton: true,
+ closeOnClick: true
+ })
+ })
+ );
+ // Add the tile boundaries control to the map.
+ map.addControl(
+ new MaplibreTileBoundaries({
+ showBoundaries: false
+ })
+ );
+ }
+ }, [map]);
return (
<div className={styles.wrap}>
+ <GeocoderSearch url="https://demo.baremaps.com/api/geocoder" map={map} />
<div ref={mapContainer} className={styles.map} />
</div>
);
diff --git a/src/components/titles/IconTitle/index.tsx
b/src/components/titles/IconTitle/index.tsx
new file mode 100644
index 0000000..378b4e1
--- /dev/null
+++ b/src/components/titles/IconTitle/index.tsx
@@ -0,0 +1,21 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import styles from './style.module.css';
+import { type IconDefinition } from '@fortawesome/fontawesome-svg-core';
+
+interface IconTitleProps extends React.HTMLAttributes<HTMLDivElement> {
+ children: React.ReactNode;
+ icon: IconDefinition;
+}
+
+export const IconTitle: React.FC<IconTitleProps> = ({
+ children,
+ icon,
+ ...props
+}) => {
+ return (
+ <div className={styles['title-container']} {...props}>
+ <FontAwesomeIcon icon={icon} fontSize={40} />
+ {children}
+ </div>
+ );
+};
diff --git a/src/components/titles/IconTitle/style.module.css
b/src/components/titles/IconTitle/style.module.css
new file mode 100644
index 0000000..766aeec
--- /dev/null
+++ b/src/components/titles/IconTitle/style.module.css
@@ -0,0 +1,7 @@
+.title-container {
+ display: flex;
+ align-items: center;
+ justify-content: start;
+ gap: 1.5rem;
+ margin-bottom: 0.75rem;
+}
diff --git a/src/pages/_app.mdx b/src/pages/_app.mdx
index 215d7b7..f2d7a40 100644
--- a/src/pages/_app.mdx
+++ b/src/pages/_app.mdx
@@ -2,6 +2,8 @@ import 'maplibre-gl/dist/maplibre-gl.css';
import '@/lib/maplibre/dist/maplibre-gl-inspect/maplibre-gl-inspect.css';
import
'@/lib/maplibre/dist/maplibre-gl-tile-boundaries/maplibre-gl-tile-boundaries.css';
+import '@fortawesome/fontawesome-svg-core/styles.css';
+
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />;
}
diff --git a/src/pages/index.mdx b/src/pages/index.mdx
index 59c90be..e4b0f2b 100644
--- a/src/pages/index.mdx
+++ b/src/pages/index.mdx
@@ -5,11 +5,15 @@ title: Mapping Infrastructure Made Easy
import Link from 'next/link';
import Image from 'next/image';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faMap } from '@fortawesome/free-regular-svg-icons';
+import {
+ faListCheck,
+ faVectorSquare,
+ faEarthAmericas,
+ faPeopleGroup
+} from '@fortawesome/free-solid-svg-icons';
import Map from '@/components/map';
import { Features, Feature } from '@/components/features';
-
-import styles from './index.module.css';
+import { IconTitle } from '@/components/titles/IconTitle';
<div className="home-content">
<div className="content-container">
@@ -21,7 +25,7 @@ import styles from './index.module.css';
online maps.
</div>
<div className="subtitle">
- <Link className={styles.cta} href="/documentation">
+ <Link href="/documentation">
Get started <span>→</span>
</Link>
</div>
@@ -33,7 +37,9 @@ import styles from './index.module.css';
<Map />
</Feature>
<Feature index={0}>
- <h3>Data Pipeline</h3>
+ <IconTitle icon={faListCheck}>
+ <h3>Data Pipeline</h3>
+ </IconTitle>
<div>
With Baremaps, you can easily create custom data pipelines to
consolidate your spatial datasets in PostGIS. Baremaps supports
@@ -43,7 +49,9 @@ import styles from './index.module.css';
</div>
</Feature>
<Feature index={0}>
- <h3>Vector Tiles</h3>
+ <IconTitle icon={faVectorSquare}>
+ <h3>Vector Tiles</h3>
+ </IconTitle>
<div>
Baremaps allows you to easily serve and publish custom vector tiles
from PostGIS. Whether you need to create maps for web or mobile
@@ -52,7 +60,21 @@ import styles from './index.module.css';
</div>
</Feature>
<Feature index={0}>
- <h3>Contributing</h3>
+ <IconTitle icon={faEarthAmericas}>
+ <h3>Geocoder</h3>
+ </IconTitle>
+ <div>
+ Baremaps provides a geocoder that allows you to search for places
+ and addresses. The geocoder is based on the GeoNames database and
+ can be easily integrated into your existing applications. We are
+ working on adding more features and improving the quality of the
+ results.
+ </div>
+ </Feature>
+ <Feature index={0}>
+ <IconTitle icon={faPeopleGroup}>
+ <h3>Contributing</h3>
+ </IconTitle>
<div>
Whether you're an experienced or just getting started, there are
many ways to get involved. We are experimenting with a range of new