<?xml version="1.0" encoding="utf-8" standalone="yes"?><feed xmlns="http://www.w3.org/2005/Atom">
  <title></title>
  <subtitle></subtitle>
  <id>https://www.endpointdev.com/blog/tags/open-source/</id>
  <link href="https://www.endpointdev.com/blog/tags/open-source/"/>
  <link href="https://www.endpointdev.com/blog/tags/open-source/" rel="self"/>
  <updated>2025-09-29T00:00:00+00:00</updated>
  <author>
    <name>End Point Dev</name>
  </author>
  
    <entry>
      <title>Making Blog Search Smarter with LLMs and Open WebUI</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2025/09/llm-expanded-vector-search/"/>
      <id>https://www.endpointdev.com/blog/2025/09/llm-expanded-vector-search/</id>
      <published>2025-09-29T00:00:00+00:00</published>
      <author>
        <name>Edgar Mlowe</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2025/09/llm-expanded-vector-search/stained-glass-flowering.webp&#34; alt=&#34;An ornate pattern flowers out from a circular window in the center of the image, framing plant-shaped stained glass depicting European church images&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen, 2024. --&gt;
&lt;p&gt;We recently released LLM Expanded Search for our blog&amp;rsquo;s vector search. It builds on what we covered in our earlier posts about &lt;a href=&#34;/blog/2025/08/vector-search-for-the-end-point-blog/&#34;&gt;AI-powered search&lt;/a&gt; and &lt;a href=&#34;/blog/2025/07/vector-search/&#34;&gt;vector search basics&lt;/a&gt;. Here&amp;rsquo;s how we built it with our internal AI setup (Open WebUI running an OpenAI-compatible API), why it makes search better, and what&amp;rsquo;s coming next.&lt;/p&gt;
&lt;h3 id=&#34;what-llm-expanded-search-actually-does&#34;&gt;What &amp;ldquo;LLM Expanded Search&amp;rdquo; actually does&lt;/h3&gt;
&lt;p&gt;Here&amp;rsquo;s the basic idea: when you search for something, we first ask an LLM to come up with related terms and phrases. Then we search for all of those terms, not just your original query.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your search gets expanded by an open-source LLM through our AI portal (Open WebUI with an OpenAI-compatible API)&lt;/li&gt;
&lt;li&gt;Those extra terms give our vector index more ways to find posts that match what you&amp;rsquo;re looking for&lt;/li&gt;
&lt;li&gt;We combine the results, remove duplicates, and sort by relevance before showing the best matches with snippets and links&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This really helps with short or vague searches where regular vector search might miss the relevant context — for example, &amp;ldquo;S3&amp;rdquo; refers to Amazon S3, which is a cloud object storage system, so whereas &amp;ldquo;S3&amp;rdquo; doesn&amp;rsquo;t provide enough context for a useful vector search. An LLM can expand this short search and include context about cloud object storage in general, as well as give enough context to return results about S3.&lt;/p&gt;
&lt;h3 id=&#34;how-it-works&#34;&gt;How it works&lt;/h3&gt;
&lt;p&gt;The frontend is pretty straightforward: our search bar has two options, &amp;ldquo;Search&amp;rdquo; (just hit Enter) and &amp;ldquo;LLM Expanded Search&amp;rdquo; (Shift/​Ctrl/​Command+Enter).&lt;/p&gt;
&lt;p&gt;When you use expanded search, here&amp;rsquo;s what happens:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We call our Open WebUI endpoint with a prompt that asks for 8–15 related terms&lt;/li&gt;
&lt;li&gt;We turn both your original query and the expanded terms into embeddings&lt;/li&gt;
&lt;li&gt;We search our vector store with all these terms and combine the results&lt;/li&gt;
&lt;li&gt;Caching and rate limiting keep things fast and cheap&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;rsquo;s a simple example of how we expand queries:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;openai&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; OpenAI
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;os&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;client = OpenAI(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    base_url=os.getenv(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;OPENAI_BASE_URL&amp;#34;&lt;/span&gt;),   &lt;span style=&#34;color:#888&#34;&gt;# e.g., http://openwebui.local/api/v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    api_key=os.getenv(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;OPENAI_API_KEY&amp;#34;&lt;/span&gt;)      &lt;span style=&#34;color:#888&#34;&gt;# token managed in your environment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#06b;font-weight:bold&#34;&gt;expand_query&lt;/span&gt;(raw_query: &lt;span style=&#34;color:#038&#34;&gt;str&lt;/span&gt;) -&amp;gt; &lt;span style=&#34;color:#038&#34;&gt;list&lt;/span&gt;[&lt;span style=&#34;color:#038&#34;&gt;str&lt;/span&gt;]:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    messages = [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;role&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;system&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;: (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;You expand a short search query into a concise, comma-separated list of &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;synonyms and closely related phrases (8–15 items). No explanations.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        {&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;role&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;user&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;content&amp;#34;&lt;/span&gt;: raw_query}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    res = client.chat.completions.create(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        model=os.getenv(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;OPENAI_MODEL&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;local-llm&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        messages=messages,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        temperature=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        max_tokens=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;200&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    )
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    text = res.choices[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;].message.content
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;return&lt;/span&gt; [t.strip() &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; t &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; text.split(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;,&amp;#34;&lt;/span&gt;) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; t.strip()]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After that, we embed the original query and the expanded terms, search the vector index, then sort by score and drop duplicates so each post appears once. Finally, we render concise snippets.&lt;/p&gt;
&lt;p&gt;For example, after a similarity search you can rank and de-duplicate like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# given: results = [(doc, score), ...]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;valid = [(d, &lt;span style=&#34;color:#038&#34;&gt;float&lt;/span&gt;(s)) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; d, s &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; results &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;float&lt;/span&gt;(s) &amp;gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0.05&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;valid.sort(key=&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;lambda&lt;/span&gt; x: x[&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;], reverse=&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;True&lt;/span&gt;)  &lt;span style=&#34;color:#888&#34;&gt;# highest score first&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;seen = &lt;span style=&#34;color:#038&#34;&gt;set&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;unique = []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; doc, score &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; valid:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    src = doc.metadata.get(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;source&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; src &lt;span style=&#34;color:#080&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; seen:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        unique.append((doc, score))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        seen.add(src)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# unique now holds top ranked, de‑duplicated posts&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;why-we-chose-open-webui&#34;&gt;Why we chose Open WebUI&lt;/h3&gt;
&lt;p&gt;A few reasons made Open WebUI the right choice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It&amp;rsquo;s open source and works great self-hosted&lt;/li&gt;
&lt;li&gt;The OpenAI-compatible API means we can drop it into existing code&lt;/li&gt;
&lt;li&gt;We can use whatever models and inference backends we want&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s easy to experiment with different prompts and workflows&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;whats-next-moving-more-into-open-webui&#34;&gt;What&amp;rsquo;s next: Moving more into Open WebUI&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;re looking into moving more of the search pipeline directly into Open WebUI workflows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Query expansion (LLM)&lt;/li&gt;
&lt;li&gt;Vector retrieval (custom tool that hits our index)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This would give us tighter integration, fewer network calls, and simpler deployment, and make it easier to try new approaches.&lt;/p&gt;
&lt;h3 id=&#34;what-youll-notice-when-using-it&#34;&gt;What you&amp;rsquo;ll notice when using it&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Short searches work way better, you get more relevant results and fewer dead ends&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s still experimental, so sometimes results might drift into related topics. Stick with regular &amp;ldquo;Search&amp;rdquo; if you want more exact matches&lt;/li&gt;
&lt;li&gt;We cache common terms to keep things smooth&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Give it a try at &lt;a href=&#34;/blog/&#34;&gt;our blog&lt;/a&gt;. Just use the search bar in our header: press Enter for regular search, or Shift/​Ctrl/​Command+Enter for LLM Expanded Search.&lt;/p&gt;
&lt;p&gt;Want to know more about why we built this? Check out the announcement and vector search posts linked above.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested in setting up LLM-expanded vector search or running something similar self-hosted with Open WebUI, we&amp;rsquo;d love to &lt;a href=&#34;/contact/&#34;&gt;help out&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Announcing End Point Ecommerce, Our New Open Source Project</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2025/08/announcing-end-point-ecommerce-our-new-open-source-project/"/>
      <id>https://www.endpointdev.com/blog/2025/08/announcing-end-point-ecommerce-our-new-open-source-project/</id>
      <published>2025-08-21T00:00:00+00:00</published>
      <author>
        <name>Kevin Campusano</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2025/08/announcing-end-point-ecommerce-our-new-open-source-project/railing-blue-sky.webp&#34; alt=&#34;A white plastered railing from a low angle, behind which is a pure blue sky. The railing comes down and to the right from the top left corner of the image, then has a right angle in the center of the image, coming back up and to the right for another quarter of the image before stopping.&#34;&gt;&lt;/p&gt;
&lt;!-- Photo by Seth Jensen, 2025. --&gt;
&lt;p&gt;Today we&amp;rsquo;re pleased to announce a new open source project: End Point Ecommerce.&lt;/p&gt;
&lt;p&gt;End Point Ecommerce is a minimalist ecommerce system that&amp;rsquo;s quick to set up and easy to understand. It is meant for developers to own, adapt, customize, and extend.&lt;/p&gt;
&lt;p&gt;You can learn more about it on &lt;a href=&#34;https://ecommerce.endpointdev.com/&#34;&gt;our landing page&lt;/a&gt;. You can also fork it today on &lt;a href=&#34;https://github.com/EndPointCorp/end-point-ecommerce&#34;&gt;GitHub&lt;/a&gt;. There are running demos here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://demo.ecommerce.endpointdev.com/swagger/index.html&#34;&gt;REST API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://admin.demo.ecommerce.endpointdev.com&#34;&gt;Admin Portal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://demo.ecommerce.endpointdev.com&#34;&gt;Web Store&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Are you interested in using End Point Ecommerce for your ecommerce site? Feel free to &lt;a href=&#34;/contact/&#34;&gt;contact us&lt;/a&gt; and we can work together to help you deploy, customize, and maintain it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/08/announcing-end-point-ecommerce-our-new-open-source-project/epec_swagger.webp&#34; alt=&#34;REST API&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/08/announcing-end-point-ecommerce-our-new-open-source-project/epec_admin_portal.webp&#34; alt=&#34;Admin Portal&#34;&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/08/announcing-end-point-ecommerce-our-new-open-source-project/epec_web_store.webp&#34; alt=&#34;Web Store&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;key-features&#34;&gt;Key features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Built with &lt;a href=&#34;https://dotnet.microsoft.com/en-us/apps/aspnet&#34;&gt;ASP.NET&lt;/a&gt;, &lt;a href=&#34;https://www.postgresql.org/&#34;&gt;PostgreSQL&lt;/a&gt;, and &lt;a href=&#34;https://www.authorize.net/&#34;&gt;Authorize.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Multiple deployment strategies, including &lt;a href=&#34;https://docs.docker.com/compose/&#34;&gt;Docker Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Code base designed to follow &lt;a href=&#34;https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/common-web-application-architectures#clean-architecture&#34;&gt;Clean Architecture&lt;/a&gt; and &lt;a href=&#34;https://learn.microsoft.com/en-us/archive/msdn-magazine/2009/february/best-practice-an-introduction-to-domain-driven-design&#34;&gt;Domain Driven Design&lt;/a&gt; concepts, without being dogmatic about it — simplicity trumps all&lt;/li&gt;
&lt;li&gt;Good test coverage with &lt;a href=&#34;https://xunit.net/&#34;&gt;xUnit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Includes a backend admin portal for managing the store, a REST API for building user-facing store frontends, and a simple web frontend&lt;/li&gt;
&lt;li&gt;Limited functionality — implements the bare minimum of ecommerce use cases&lt;/li&gt;
&lt;li&gt;Product catalog, shopping cart, order submission, email delivery&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;how-we-got-here&#34;&gt;How we got here&lt;/h3&gt;
&lt;p&gt;We recently had the opportunity to help one of our clients migrate their ecommerce site. They were using an established ecommerce engine and were having various problems with it. Their requirements were simple, and they didn&amp;rsquo;t need all the bells and whistles of big ecommerce solutions. They didn&amp;rsquo;t want to deal with the overhead, bloat, and difficulty of finding maintainers.&lt;/p&gt;
&lt;p&gt;During initial investigation and planning, we didn&amp;rsquo;t find any options that were small and easy to set up, understand, and customize, so we decided to build them a new site from scratch.&lt;/p&gt;
&lt;p&gt;We realize that there may be others out there in the same boat that we were. You want to set up a custom ecommerce site but you don&amp;rsquo;t want to deal with the complexity of bigger off-the-shelf solutions. You want something which you can thoroughly understand quickly, and modify to your heart&amp;rsquo;s content, as if it were your own code base that you developed from scratch.&lt;/p&gt;
&lt;h3 id=&#34;who-is-this-for&#34;&gt;Who is this for?&lt;/h3&gt;
&lt;p&gt;This project is meant for .NET developers who want a solid and simple foundation to build and customize ecommerce sites. To that end, the feature set is very basic, the code base is very straightforward, and the documentation is very developer-oriented. The idea being that developers can quickly get up to speed with how everything works, develop ownership over the code base, and implement their bespoke use cases.&lt;/p&gt;
&lt;p&gt;This project is not meant for non-technical folks. There is no installation wizard, no templating engine, no plugin framework, no no-code customization capabilities.&lt;/p&gt;
&lt;p&gt;If you want to set up a simple online store, or develop a highly customized one from scratch, think of this as a way of skipping the first few steps of building the foundational architecture and basic functionality.&lt;/p&gt;
&lt;p&gt;Please check it out! And do whatever you want with it!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>New Cesium KML-CZML Editor Features: Custom Data &amp; Styling, Google 3D Tiles, and More</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2025/06/new-czml-kml-editor-features/"/>
      <id>https://www.endpointdev.com/blog/2025/06/new-czml-kml-editor-features/</id>
      <published>2025-06-13T00:00:00+00:00</published>
      <author>
        <name>Dmitry Kiselev</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2025/06/new-czml-kml-editor-features/banner.webp&#34; alt=&#34;To the right, a satellite imagery map with blue and red gradient polygons drawn over it. To the left, controls for an app, including import/export, create entities, and a list of entities.&#34;&gt;&lt;/p&gt;
&lt;p&gt;I have made some updates to the &lt;a href=&#34;https://www.visionport.com/cesium-kml-czml-editor/&#34;&gt;Cesium KML-CZML editor&lt;/a&gt; I created and maintain.&lt;/p&gt;
&lt;p&gt;The most important additions and changes are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Support for Google 3D tiles&lt;/li&gt;
&lt;li&gt;Support for writing many more features, including interpolation and time series data for some properties. There are still no editing capabilities for these properties, but while previously the editor would strip these values from the data, it will now copy them into the output file.&lt;/li&gt;
&lt;li&gt;Export to KML and KMZ&lt;/li&gt;
&lt;li&gt;Support for custom data and styling using that custom data&lt;/li&gt;
&lt;li&gt;Switched frontend framework from Vue to React&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Adding support for Google 3D tiles is what caused me to create this major version update. In a nutshell, Cesium has its own way of adding reactivity to Entities and Vue doesn&amp;rsquo;t always play nice with it. If I add Google 3D tiles to the scene, it looks like that Cesium Entities have some references to the scene and that causes Vue to apply reactive getters and setters to the whole scene.&lt;/p&gt;
&lt;p&gt;So, I&amp;rsquo;ve switched to using React because it&amp;rsquo;s easier to control which parts should be reactive, as well as when and how you update Cesium Entities and UI components.&lt;/p&gt;
&lt;p&gt;The next important piece is the CZML exporter. The main issue here is that I don&amp;rsquo;t want to strip away features even if I don&amp;rsquo;t support editing them.&lt;/p&gt;
&lt;p&gt;Even if I don&amp;rsquo;t have the UI to edit certain properties or animations for properties, a user can load a CZML document with a mixture of supported and unsupported features without losing the unsupported features. That means that the exporter should be more robust and feature complete than editor itself.&lt;/p&gt;
&lt;p&gt;I will probably separate the exporter as standalone package because it is valuable on its own — I couldn&amp;rsquo;t find a way to export Cesium entities as CZML within the library.&lt;/p&gt;
&lt;h3 id=&#34;custom-data--styling&#34;&gt;Custom data &amp;amp; styling&lt;/h3&gt;
&lt;p&gt;This part is my favorite. I have some experience creating maps, and when you are working with maps you are not focused on polygons or billboards, you are focused on the data, while graphics are just a tool to represent it.&lt;/p&gt;
&lt;p&gt;The previous iteration of the KML-CZML editor was more oriented toward importing KML and massaging it to have the same look in Cesium as it would in Google Earth. That puts focus on graphical features. This time I wanted to make it easy to edit data and style things based on the data.&lt;/p&gt;
&lt;h4 id=&#34;demo&#34;&gt;Demo&lt;/h4&gt;
&lt;p&gt;We&amp;rsquo;ll load in some demo data from New York elections in &lt;code&gt;Mike4326.geojson&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/06/new-czml-kml-editor-features/map-with-geojson.webp&#34; alt=&#34;The CZML editor with all yellow polygons drawn over a region of New York&#34;&gt;&lt;/p&gt;
&lt;p&gt;From here, we&amp;rsquo;ll click on the &amp;ldquo;Data table &amp;amp; Conditional Styling&amp;rdquo; button. Then, in the menu that opens, we&amp;rsquo;ll set up Color by value so that we can display different regions differently by our selected attribute. Note the &amp;ldquo;Mike_prc&amp;rdquo; column which displays a percent value. The app will select a color from the gradient based on this value.The app will select a color from the gradient based on this value.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/06/new-czml-kml-editor-features/color-by-value-selecting-Mike_prc.webp&#34; alt=&#34;The editor with a full screen popup reading &amp;ldquo;conditional styling&amp;rdquo;. There are three main tabs, &amp;ldquo;Color by value&amp;rdquo;, &amp;ldquo;Labels&amp;rdquo;, and &amp;ldquo;Extrusion and scale&amp;rdquo;. The first is selected. Under the tabs it reads &amp;ldquo;Set entities colors by value of an attribute&amp;rdquo;. Under a dropdown reading &amp;ldquo;Attribute&amp;rdquo;, &amp;ldquo;Mike_prc&amp;rdquo; is selected. A blue to red gradient is below, with a continuous range of colors. At the bottom is a table with polygon 1, polygon 2, etc. Then a style column, all of which have a yellow box. The rightmost column is &amp;ldquo;Mike_prc&amp;rdquo;, and reads a percent number from 0 to 100.&#34;&gt;&lt;/p&gt;
&lt;p&gt;To group values together, we&amp;rsquo;ll toggle the &amp;ldquo;Fixed gradations&amp;rdquo; switch. Then hit the &amp;ldquo;Preview&amp;rdquo; button. By default, there are 9 gradations.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/06/new-czml-kml-editor-features/fixed-gradations-9-preview.webp&#34; alt=&#34;The same view, with the gradient now reduced to only 9 values. There is an additional column next to &amp;ldquo;style&amp;rdquo; which shows colors from the gradient corresponding to the Mike_prc value.&#34;&gt;&lt;/p&gt;
&lt;p&gt;We want fewer gradations than that. Let&amp;rsquo;s try out 6.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/06/new-czml-kml-editor-features/fixed-gradations-6-preview.webp&#34; alt=&#34;The same view, but now there are only 6 values in the gradient. Most of the preview colors are the same, but a few have been changed to match the 6 values.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Not much difference, but we&amp;rsquo;ll go with the 6 gradations. I&amp;rsquo;ll hit apply, and you can see the colors are now displayed in the &amp;ldquo;Style&amp;rdquo; column, while the &amp;ldquo;Preview&amp;rdquo; column has been hidden.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/06/new-czml-kml-editor-features/fixed-gradations-6-applied.webp&#34; alt=&#34;The same view, but now the red to blue colors appear in the &amp;ldquo;style&amp;rdquo; column, and the &amp;ldquo;preview column is hidden.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see how it looks on the map.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/06/new-czml-kml-editor-features/map-with-6-gradations.webp&#34; alt=&#34;The original view of the CZML editor. The electoral districts now show a gradient from blue to red, corresponding to the polygon styling we saw, rather than all being yellow as it started.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Very nice! Now let&amp;rsquo;s add some labels for a different attribute, the election district.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/06/new-czml-kml-editor-features/label-Election_D-selected.webp&#34; alt=&#34;The conditional styling menu, this time in the &amp;ldquo;Labels&amp;rdquo; tab. Text reads &amp;ldquo;Set Labels and Label text using entities attribute value. The selected attribute is &amp;ldquo;Election_D&amp;rdquo;. There is a box displaying pure black color above the table of polygons.&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can see the text color defaults to black, which won&amp;rsquo;t read well on our colored districts. Let&amp;rsquo;s change it by hitting the &amp;ldquo;edit&amp;rdquo; button next to the color.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/06/new-czml-kml-editor-features/label-color-selected.webp&#34; alt=&#34;The same screen, now with added color sliders next to the color square. The color has been changed to a near-white gray.&#34;&gt;&lt;/p&gt;
&lt;p&gt;I changed it to a light gray. Let&amp;rsquo;s look at the map now.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/06/new-czml-kml-editor-features/map-with-labels.webp&#34; alt=&#34;A closer view of the map with the red-blue colored districts, now with many light gray labels displaying the name of the election district.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Lots of labels! This is quite a dense area, so you would probably want to zoom in further to see the labels more clearly.&lt;/p&gt;
&lt;p&gt;See my previous blog post &lt;a href=&#34;/blog/2020/12/cesium-kml-czml-editor/&#34;&gt;introducing the Cesium CZML-KML Editor&lt;/a&gt; for more about the Cesium CZML-KML editor.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Place Labels in Cesium using Vector Data Tiles</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2025/02/cesium-place-label-vector-data-tiles/"/>
      <id>https://www.endpointdev.com/blog/2025/02/cesium-place-label-vector-data-tiles/</id>
      <published>2025-02-13T00:00:00+00:00</published>
      <author>
        <name>Dmitry Kiselev</name>
      </author>
      <content type="html">
        &lt;style&gt;
ol &gt; * &gt; ol {
  list-style: lower-alpha;
}
&lt;/style&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/02/cesium-place-label-vector-data-tiles/banner.webp&#34; alt=&#34;A satellite map of a large area around New York City, with labels for cities scaled to their population.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Cesium is like an IKEA globe: it can do almost anything, but you have to put it together yourself. One major missing feature is labels for cities. Compare the default views of Google Earth and Cesium, and you&amp;rsquo;ll notice this immediately.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/02/cesium-place-label-vector-data-tiles/google-vs-cesium-default-view.webp&#34; alt=&#34;A side-by-side view of satellite imagery of the northeastern United States. On the left, there are state labels and state lines, with some large cities marked. On the right, there are no markings, just the satellite imagery.&#34;&gt;&lt;br&gt;
&lt;small&gt;Left: Google Earth default view, with labels and state lines. Right: Cesium default view, with no labels or markers.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re reading this post, you might be able to find any major U.S. city without any clues, but most people can’t. You can overlay imagery that includes labels as part of the image, but this approach has a significant drawback: as soon as you rotate the view away from north-up, the labels become difficult to read.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/02/cesium-place-label-vector-data-tiles/imagery-with-labels-in-layer.webp&#34; alt=&#34;A side-by-side comparison of satellite imagery. There are labels for major roads and cities. On the left, these look normal as one would expect on a map, while on the right, the view has been tilted up to a higher angle, and the labels have been tilted as well, making them unreadable.&#34;&gt;&lt;/p&gt;
&lt;p&gt;In Google Earth, this issue is handled using vector data overlays. Labels, borders, and other similar elements are examples of how useful vector overlays can be.&lt;/p&gt;
&lt;p&gt;Cesium also supports vector features such as labels, polylines, and polygons. You can create them manually or use CZML or KML to add such features to a scene. If you use KML, Cesium translates it internally, but the result is the same. Vector features have distinct advantages for 3D geographic data, with labels being the most obvious example.&lt;/p&gt;
&lt;h3 id=&#34;challenges-of-vector-data-in-cesium&#34;&gt;Challenges of Vector Data in Cesium&lt;/h3&gt;
&lt;p&gt;Displaying vector data in Cesium comes with some challenges. Since we cannot load and display all data at once, we have two options: keep it as vector data or rasterize it into imagery. Either way, we need to tile the data, breaking it into small chunks and generalizing or simplifying it.&lt;/p&gt;
&lt;p&gt;Continuing with the example of labels: tiles covering large areas should include only essential labels (e.g., capital cities) to avoid excessive clutter. Similarly, for polylines such as roads or boundaries, we need to prioritize important roads and simplify their geometries.&lt;/p&gt;
&lt;p&gt;Most GeoServers and data providers serve vector tiles encoded as GeoJSON, KML, or other binary formats. However, integrating them into Cesium or Google Earth requires additional steps.&lt;/p&gt;
&lt;p&gt;There are two big considerations that need to be taken into account when integrating tiled vector data into any application:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How particular geometric features (points, lines, and polygons) are encoded for every tile. Also, what are their attributes and how should they be represented and styled? This involves considering whether a point should have a label or an icon, what color should it be, etc.&lt;/li&gt;
&lt;li&gt;How the data is arranged. This includes how many levels it has, how you fetch new tiles, what the area each tile covers is, and how the software you are integrating with (in our case, Cesium) manages rendering, loading, and unloading the data.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In Google Earth, KML covers both issues in one technology. KML describes features and their styling, while for the arrangement of tiles you can use &lt;a href=&#34;https://www.google.com/earth/outreach/learn/using-network-links-effectively/&#34;&gt;Network Link Regions&lt;/a&gt;. To put it simply, Network Link Regions define an area and as soon as the camera gets close enough to that area, Google Earth loads a new chunk of data for that area. You can read more about that in the &lt;a href=&#34;https://developers.google.com/kml/documentation/regions#smart-loading-of-region-based-network-links&#34;&gt;KML docs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In Cesium, this process is covered by multiple technologies, and not all of them work together. We have several encoding and styling options (CZML, KML, GeoJSON), but none of them natively support tiling.&lt;/p&gt;
&lt;p&gt;On the other hand, Cesium 3D Tiles work well for large 3D meshes and point clouds, but they do not support CZML, KML, or GeoJSON as tile content.&lt;/p&gt;
&lt;p&gt;To bridge this gap, Cesium provides &lt;a href=&#34;https://github.com/CesiumGS/3d-tiles/tree/vctr/TileFormats/VectorData&#34;&gt;the Vector Data tile format&lt;/a&gt; and &lt;a href=&#34;https://github.com/CesiumGS/3d-tiles/tree/main/specification/Styling&#34;&gt;3D Tiles Styling&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Although they are still experimental, these features work with the standard Cesium distribution without any hacks or undocumented options.&lt;/p&gt;
&lt;p&gt;Since I previously implemented city labels in Cesium, I decided to revisit the task to do it with this new standard in mind. In my earlier implementation, I had to rely on undocumented Cesium features to determine which tiles should be loaded and unloaded. More details about that approach can be found &lt;a href=&#34;/blog/2023/05/cesium-labels/&#34;&gt;in my previous blog post&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;technical-implementation&#34;&gt;Technical Implementation&lt;/h3&gt;
&lt;p&gt;For this tileset generation, I use two standards:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vector Data tile content with tileset styling
&lt;ul&gt;
&lt;li&gt;Vector Data tile content defines how each tile is encoded&lt;/li&gt;
&lt;li&gt;Tileset styling determines how points, lines, and polygons are rendered&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Implicit tiling
&lt;ul&gt;
&lt;li&gt;Implicit tiling defines tile arrangement and availability&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both standards use a mix of JSON and binary-encoded features. In a nutshell, to encode a point with a label you have to write a vctr file, a subtree file and a tileset.json file.&lt;/p&gt;
&lt;p&gt;Each &lt;strong&gt;vctr&lt;/strong&gt; file consists of the following sections:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Header&lt;/strong&gt;: Defines technical data, like the size of the sections in bytes and their offsets&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature Table&lt;/strong&gt;: Defines how to read points, lines, or polygons from the binary data section In our case we just specify how many points we have&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Batch Table&lt;/strong&gt;: Defines data specific attributes, in our case that will be a name for a point&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Binary data with positions and indices&lt;/strong&gt;: Coordinates of the points optimized for storage size&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Subtree&lt;/strong&gt; files encode which child tiles are available, which of them have content, and what are the available subtrees.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;tileset.json&lt;/strong&gt; puts all that information together, defining what region the whole tileset covers, specifying that we are using implicit tiling, and defining URL templates for fetching &lt;strong&gt;vctr&lt;/strong&gt; and &lt;strong&gt;subtree&lt;/strong&gt; files.&lt;/p&gt;
&lt;p&gt;Add a style which tells Cesium that it should create a label for every point with its name attribute. And congratulations, we have our first set of labels!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2025/02/cesium-place-label-vector-data-tiles/earth-with-labels.webp&#34; alt=&#34;Satellite imagery on a globe. There are labels on some of the biggest cities on the globe, such as Los Angeles, New York City, Bogota, etc.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;managing-uneven-label-distribution&#34;&gt;Managing Uneven Label Distribution&lt;/h3&gt;
&lt;p&gt;But as soon as we zoom in we hit a problem: we have too many labels too close to each other.&lt;/p&gt;
&lt;img src=&#34;/blog/2025/02/cesium-place-label-vector-data-tiles/uneven-label-distribution.webp&#34; alt=&#34;The northeastern United States, with labels on the largest cities. Most labels overlap with several others close by, while there are large areas with no labels at all.&#34; style=&#34;max-height:500px&#34;&gt;
&lt;p&gt;In order to avoid having the globe polluted with messy clumps of labels when zooming in, we have to discern how the visibility of 3D tile content is determined by Cesium. A key concept for handling this is assigning a &lt;code&gt;geometricError&lt;/code&gt; value to each tile. The exact value might be complex to calculate, but the general rule is: the larger the &lt;code&gt;geometricError&lt;/code&gt;, the bigger the distance from which a tile should be visible.&lt;/p&gt;
&lt;p&gt;So as you zoom in closer, labels from tiles with bigger &lt;code&gt;geometricError&lt;/code&gt; will appear sooner. Furthermore, for &lt;em&gt;implicit tiling&lt;/em&gt; the &lt;code&gt;geometricError&lt;/code&gt; of children tiles should be half of what their parent tiles’ &lt;code&gt;geometricError&lt;/code&gt; values are. Practically speaking, this means that the &lt;code&gt;geometricError&lt;/code&gt; value assigned to the root tile cascades throughout the system and changes how soon all of the labels become visible. To have some of the labels appear later than the others we have to push them down the tile tree.&lt;/p&gt;
&lt;p&gt;The first thing I tried was to set a minimum tree depth for a label based on city size, but the end result was pretty much the same:&lt;/p&gt;
&lt;img src=&#34;/blog/2025/02/cesium-place-label-vector-data-tiles/uneven-label-distribution-minimum-tree-depth.webp&#34; alt=&#34;The northeastern United States, looking essentially unchanged from the previous image.&#34; style=&#34;max-height:500px&#34;&gt;
&lt;p&gt;The root cause of the points clumping up is not small towns appearing too early, it’s how the populated places themself are distributed. “The Bronx” and “Manhattan” are just big and too close to each other and to “New York City”. If I try to push “Manhattan” deep enough so it doesn’t overlap with “New York City” based just on its size we won’t be able to see Boston or Baltimore or Hamilton, although they have plenty of space to be shown at this zoom level. Hence, we need a way to cluster points and determine point depth based on how much space there is between neighbours.&lt;/p&gt;
&lt;h3 id=&#34;clustering-labels&#34;&gt;Clustering Labels&lt;/h3&gt;
&lt;p&gt;Clustering is a well-established problem in computational geometry, and there are plenty of libraries to solve it, but if I used one of them I would also have to integrate it with the tree structure of the tiles. So instead I’ve implemented a straightforward approach that is effective, although not 100% correct mathematically:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For every level of the tree, calculate a distance threshold. The threshold gets smaller and smaller with every level of the tree. Let’s follow the same pattern as the tiles&amp;rsquo; &lt;code&gt;geometricError&lt;/code&gt; for this threshold&lt;/li&gt;
&lt;li&gt;For every point to be inserted into the tree, calculate the distance to a closest point in the tree&lt;/li&gt;
&lt;li&gt;
&lt;ol&gt;
&lt;li&gt;If the distance is more than the threshold, just insert the point. If the distance is smaller than the threshold, let’s calculate how many levels we should skip to get the threshold small enough to insert the point at this level&lt;/li&gt;
&lt;li&gt;If an inserted point is more important than an existing point, then put the inserted point at the current level and push the old one down the tree instead&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And so, there you have it:&lt;/p&gt;
&lt;img src=&#34;/blog/2025/02/cesium-place-label-vector-data-tiles/working-labels.webp&#34; alt=&#34;The same northeastern United States area, with the labels mostly not overlapping, and with more mid-sized towns far from big cities labeled.&#34; style=&#34;max-height:500px&#34;&gt;
&lt;h3 id=&#34;next-steps&#34;&gt;Next Steps&lt;/h3&gt;
&lt;p&gt;Further steps I have in mind to expand on this work with vector tiles include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enhancing styling options&lt;/li&gt;
&lt;li&gt;Encoding polylines&lt;/li&gt;
&lt;li&gt;Encoding polygons&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;notes&#34;&gt;Notes&lt;/h3&gt;
&lt;p&gt;The project is available on GitHub under the Apache-2.0 license: &lt;a href=&#34;https://github.com/EndPointCorp/cesium-vector-data-tiles&#34;&gt;https://github.com/EndPointCorp/cesium-vector-data-tiles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Place data is from GeoNames.org under the CC-BY license.&lt;/p&gt;
&lt;p&gt;Screenshots use Bing Imagery, ArcGIS World Imagery, and Google Earth.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Exploring Geodatabase Files</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2024/08/exploring-geodatabase-files/"/>
      <id>https://www.endpointdev.com/blog/2024/08/exploring-geodatabase-files/</id>
      <published>2024-08-14T00:00:00+00:00</published>
      <author>
        <name>Constante “Tino” Gonzalez</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2024/08/exploring-geodatabase-files/banner.webp&#34; alt=&#34;The sun shines brightly behind a cloud, casting a half halo of rays to the left of the image, and leaving the right of the image quite dim.&#34;&gt;&lt;/p&gt;
&lt;!-- Image by Jaxson Baerg --&gt;
&lt;p&gt;One of our clients recently provided us with a dataset of real estate properties that they manage, and asked us to generate content based off of the points and polygons in the dataset.&lt;/p&gt;
&lt;p&gt;We will walk through the process of extracting polygons, placemarks, and other info from a geodatabase file and converting them into separate KML files using the &lt;code&gt;ogr2ogr&lt;/code&gt; command-line tool, adding some logic to the data selection to limit the subset of features. We will also explore the GDB file using the GDAL Python library to export the data as JSON for use in other scripts.&lt;/p&gt;
&lt;h3 id=&#34;prerequisites&#34;&gt;Prerequisites&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Basic understanding of geospatial data&lt;/li&gt;
&lt;li&gt;Installed versions of the &lt;code&gt;GDAL/OGR&lt;/code&gt; library&lt;/li&gt;
&lt;li&gt;A geodatabase file (&lt;code&gt;.gdb&lt;/code&gt;, &lt;code&gt;.gdb.zip&lt;/code&gt;, or &lt;code&gt;.shp&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;a-first-look-into-the-contents-of-the-gdb-file&#34;&gt;A first look into the contents of the GDB file&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/08/exploring-geodatabase-files/pin_label_polygon.webp&#34; alt=&#34;Google earth showing a campus of several buildings, surrounded by a yellow rectangle overlay, and with a blue pin labeled &amp;ldquo;label&amp;rdquo; on one side.&#34;&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ogrinfo example.gdb.zip&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This command will list all layers that are available in the dataset. Add a specific layer as a parameter to the same command, and it will output all the fields, their types, and values for every feature in the layer.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ogrinfo example.gdb.zip a_layer_name&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The parameter &lt;code&gt;-so&lt;/code&gt; can be used to omit the values from the output and get only the layer field names and types:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;#$&amp;gt; ogrinfo example.gdb.zip Land_Points -so
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;INFO: Open of `example.gdb.zip&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      using driver `OpenFileGDB&amp;#39; successful.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Layer name: Land_Points
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Geometry: Point
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Feature Count: 219
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Extent: (-122.699891, -23.590280) - (139.763352, 59.622821)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Layer SRS WKT:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GEOGCRS[&amp;#34;WGS 84&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    DATUM[&amp;#34;World Geodetic System 1984&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ELLIPSOID[&amp;#34;WGS 84&amp;#34;,6378137,298.257223563,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            LENGTHUNIT[&amp;#34;metre&amp;#34;,1]]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PRIMEM[&amp;#34;Greenwich&amp;#34;,0,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ANGLEUNIT[&amp;#34;degree&amp;#34;,0.0174532925199433]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    CS[ellipsoidal,2],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        AXIS[&amp;#34;geodetic latitude (Lat)&amp;#34;,north,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ORDER[2],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ANGLEUNIT[&amp;#34;degree&amp;#34;,0.0174532925199433]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    USAGE[
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        SCOPE[&amp;#34;unknown&amp;#34;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        AREA[&amp;#34;World&amp;#34;],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        BBOX[-90,-180,90,180]],
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ID[&amp;#34;EPSG&amp;#34;,4326]]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Data axis to CRS axis mapping: 2,1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FID Column = OBJECTID
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Geometry Column = Shape
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;asset_type: Integer (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;asset_id: String (15.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;property_code: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;full_address_text: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_name: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_address1: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_city: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_sate_code: String (50.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_postal_code: String (10.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_country_code: String (50.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;division_name: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;region_name: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;market_name: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;submarket_name: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;supplemental_portfolio_name: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ownership_name: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_held_for_sale_acre: Real (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_held_for_sale_hect: Real (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_held_for_development_acre: Real (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_held_for_development_hect: Real (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;total_land_acre: Real (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;total_land_hectare: Real (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;buildable_area_sf: Real (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;buildable_area_sm: Real (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;buildable_area_tsubo: Real (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_latitude: Real (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;land_longitude: Real (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;geocoded: Integer (0.0) DEFAULT 0
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;globalid: String (0.0) NOT NULL
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;created_user: String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;created_date: DateTime (0.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;last_edited_user:  String (255.0)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;last_edited_date: DateTime (0.0)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This command shows you information for a single layer, which can be helpful if you are only looking for certain values. However, the geographic data is not much to look at in the terminal. To visualize it, we can convert the data to KML, which applications like Google Earth or Cesium can render while keeping the information as text that can be read:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ogr2ogr -f &amp;#34;KML&amp;#34; example_output.kml example.gdb.zip layer_name&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;-f &amp;quot;KML&amp;quot;&lt;/code&gt; specifies the output format&lt;/li&gt;
&lt;li&gt;&lt;code&gt;example_output.kml&lt;/code&gt; is the name of the output KML file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;example.gdb.zip&lt;/code&gt; is the path to the geodatabase file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;layer_name&lt;/code&gt; is the geodatabase layer to export&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This command will export into a KML file:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;All the geometries in the layer, be it placemarks or polygons in our case&lt;/li&gt;
&lt;li&gt;All the other fields of information as extended data, which will show up for each feature as a balloon table when visualized in Google Earth&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/08/exploring-geodatabase-files/full_label.webp&#34; alt=&#34;Google earth, with a pin selected. A popup dialog displays a table with Land_Points:asset_type, Land_Points:asset_id, and other Land_Points data.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;the-ogr2ogr--sql-option&#34;&gt;The &lt;code&gt;ogr2ogr&lt;/code&gt; &lt;code&gt;-sql&lt;/code&gt; option&lt;/h3&gt;
&lt;p&gt;The default balloon popup was not the outcome we wanted for this KML file. We first used &lt;code&gt;sed&lt;/code&gt; to remove all the extended data from the KML files, but after looking into it a bit further, we found an &lt;code&gt;ogr2ogr&lt;/code&gt; option, &lt;code&gt;-sql&lt;/code&gt;, that made the data filtering easier. This option lets us add a query to the command just like getting the data from a SQL database.&lt;/p&gt;
&lt;h4 id=&#34;1-extract-property-names&#34;&gt;1. Extract property names&lt;/h4&gt;
&lt;p&gt;To create a KML file with just the names of the properties, look up the layers and fields in them using &lt;code&gt;ogrinfo&lt;/code&gt; and find the points layer that has the names—in this case, &lt;code&gt;Layer_Points&lt;/code&gt;. Then, add the desired SQL query to the command.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ogr2ogr -f &amp;#34;KML&amp;#34; output_names.kml example.gdb.zip -sql &amp;#34;SELECT name FROM Layer_Points&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;2-extract-polygons-only&#34;&gt;2. Extract polygons only&lt;/h4&gt;
&lt;p&gt;The polygon geometries are stored in &lt;code&gt;Layer_Polygons&lt;/code&gt;. They can be selected using the special OGR field &lt;code&gt;OGR_GEOMETRY&lt;/code&gt; that refers to the geometry of the selected layer:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ogr2ogr -f &amp;#34;KML&amp;#34; output_polygons.kml example.gdb.zip -sql &amp;#34;SELECT OGR_GEOMETRY FROM Layer_Polygons&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&#34;/blog/2024/08/exploring-geodatabase-files/polygons_only.webp&#34; alt=&#34;Google Earth, with two irregularly shaped red outlines. One has a popup dialog with a table reading &amp;ldquo;Land_Polygons:OGR_GEOMETRY&amp;rdquo;, and a value of &amp;ldquo;MULTIPOLYGON&amp;rdquo;&#34;&gt;&lt;/p&gt;
&lt;h4 id=&#34;3-create-kml-with-pins-and-names&#34;&gt;3. Create KML with pins and names&lt;/h4&gt;
&lt;p&gt;To create a KML file with property names as placemarks with pins, we just select the name. The point placemark seems to be included with all data:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ogr2ogr -f &amp;#34;KML&amp;#34; output_pins.kml input.gdb layer_name -sql &amp;#34;SELECT name FROM layer_name&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;4-create-kml-with-pins-only-no-names&#34;&gt;4. Create KML with pins only, no names&lt;/h4&gt;
&lt;p&gt;To get the points with nothing else, we use the SQLite &lt;code&gt;MakePoint&lt;/code&gt; function, which selects a list of points from the KML.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ogr2ogr -f &amp;#34;KML&amp;#34; output_pins.kml input.gdb layer_name -dialect SQLite -sql &amp;#34;SELECT MakePoint(land_longitude, land_latitude) AS geom FROM Land_Points&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;5-extract-limited-properties&#34;&gt;5. Extract limited properties&lt;/h4&gt;
&lt;p&gt;For extracting a limited number of properties, we can add the &lt;code&gt;WHERE&lt;/code&gt; clause to the SQL command:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ogr2ogr -f &amp;#34;KML&amp;#34; output_pins.kml input.gdb layer_name -sql &amp;#34;SELECT name FROM layer_name&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We can also use the &lt;code&gt;ogr2ogr&lt;/code&gt; &lt;code&gt;-where&lt;/code&gt; flag to use that part of the query only:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ogr2ogr -f &amp;#34;KML&amp;#34; limited_output_polygons.kml input.gdb layer_name -where &amp;#34;ID IN (1, 2, 3)&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;6-run-it-all-in-a-python-script&#34;&gt;6. Run it all in a Python script&lt;/h4&gt;
&lt;p&gt;There is a Python GDAL library, which I will cover the basics of later, but first, here is a simplified example using Python &lt;code&gt;subprocess&lt;/code&gt; to run the ogr2ogr commands we tested in the terminal.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;subprocess&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gdb_file = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;example.gdb.zip&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;column_name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;p_code&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;elem_str = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&amp;#39;a1&amp;#39;, &amp;#39;a2&amp;#39;, &amp;#39;b1&amp;#39;, &amp;#39;b3&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sql_land_labels = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;SELECT land_name FROM Land_Points WHERE &lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;column_name&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; IN (&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;elem_str&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;subprocess.run([&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;ogr2ogr&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;-f&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;KML&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;land_labels.kml&amp;#39;&lt;/span&gt;, gdb_file, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;-sql&amp;#39;&lt;/span&gt;, sql_land_labels])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sql_land_points = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;SELECT MakePoint(land_longitude, land_latitude) AS geom FROM Land_Points WHERE &lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;column_name&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; IN (&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;elem_str&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;subprocess.run([&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;ogr2ogr&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;-f&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;KML&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;land_points.kml&amp;#39;&lt;/span&gt;, gdb_file, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;-dialect&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;SQLite&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;-sql&amp;#39;&lt;/span&gt;, sql_land_points])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sql_land_polygons = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;SELECT OGR_GEOMETRY FROM Land_Polygons WHERE &lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;column_name&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt; IN (&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;elem_str&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;subprocess.run([&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;ogr2ogr&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;-f&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;KML&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;land_pols.kml&amp;#39;&lt;/span&gt;, gdb_file, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;-sql&amp;#39;&lt;/span&gt;, sql_land_polygons])&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This script will create the three different KML files that we usually need for our presentations. We used variables for the &lt;code&gt;gdb_file&lt;/code&gt;, &lt;code&gt;column_name&lt;/code&gt;, and &lt;code&gt;elem_str&lt;/code&gt; command line parameters to make the script easy to use for selecting other data. We also use them in other scripts to join, apply custom styling, and add regions to the KMLs, which will be covered in another blog post.&lt;/p&gt;
&lt;h4 id=&#34;7-extract-all-data-as-json-from-one-layer-using-the-python-gdal-library&#34;&gt;7. Extract all data as JSON from one layer using the Python GDAL library&lt;/h4&gt;
&lt;p&gt;I probably should have started here, but I only used the Python library later on in the process to join the &lt;code&gt;gdp.zip&lt;/code&gt; file data with data from other sources (spreadsheets, emails, etc.).&lt;/p&gt;
&lt;p&gt;First, install the GDAL library:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;pip install gdal&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then you can use the following Python script, changing as necessary the &lt;code&gt;gdb_file&lt;/code&gt;, &lt;code&gt;layer_name&lt;/code&gt;, and the unique field chosen to structure the data. The code is explained in the comments.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;#!/bin/python&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;osgeo&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;import&lt;/span&gt; ogr
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Open the Geodatabase file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;driver = ogr.GetDriverByName(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;OpenFileGDB&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;gdb_file = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;example.gdb.zip&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;data_source = driver.Open(gdb_file, &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Get the layer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;layer_name = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Building_Points&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;layer = data_source.GetLayerByName(layer_name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; layer:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Get the layer definition&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    layer_defn = layer.GetLayerDefn()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Get the number of fields in the layer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    num_fields = layer_defn.GetFieldCount()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Initialize an empty list to store field names&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    field_names = []
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#888&#34;&gt;# Iterate over each field and add its name to the list&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; i &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;range&lt;/span&gt;(num_fields):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        field_defn = layer_defn.GetFieldDefn(i)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        field_name = field_defn.GetName()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        field_names.append(field_name)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#038&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Field names:&amp;#34;&lt;/span&gt;, field_names)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#038&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Layer &amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;layer_name&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39; not found.&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;all_info = {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Iterate over all features and organize all field names under a unique one for the dictionary structure&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; feature &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; layer:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;if&lt;/span&gt; feature &lt;span style=&#34;color:#080&#34;&gt;is&lt;/span&gt; &lt;span style=&#34;color:#080&#34;&gt;not&lt;/span&gt; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        globalid = feature.GetField(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;globalid&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        all_info[globalid] = {}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; fn &lt;span style=&#34;color:#080&#34;&gt;in&lt;/span&gt; field_names:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            all_info[globalid][fn] = feature.GetField(fn)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Close the data source&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;data_source = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Save information to a JSON file&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;output_file = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;info.json&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;with&lt;/span&gt; &lt;span style=&#34;color:#038&#34;&gt;open&lt;/span&gt;(output_file, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;w&amp;#39;&lt;/span&gt;) &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;as&lt;/span&gt; json_file:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    json.dump(all_info, json_file, indent=&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;4&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;print&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Information saved to &lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;{&lt;/span&gt;output_file&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The script will create a JSON file with all the information on the layer.&lt;/p&gt;
&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Following these steps, we can efficiently manage and visualize geospatial data using &lt;code&gt;ogr2ogr&lt;/code&gt;, SQL, and KML, and in some cases, JSON. These methods allow for a high degree of customization and can be tailored to specific project requirements.&lt;/p&gt;
&lt;h3 id=&#34;additional-resources&#34;&gt;Additional Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://gdal.org&#34;&gt;GDAL/OGR Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://gdal.org/python/&#34;&gt;GDAL Python Bindings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developers.google.com/kml/documentation&#34;&gt;KML Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.sqlite.org/docs.html&#34;&gt;SQLite Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>The Perl and Raku Conference 2024</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2024/08/the-perl-and-raku-conference-2024/"/>
      <id>https://www.endpointdev.com/blog/2024/08/the-perl-and-raku-conference-2024/</id>
      <published>2024-08-02T00:00:00+00:00</published>
      <author>
        <name>Andrew Baerg</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2024/08/the-perl-and-raku-conference-2024/next-generation.webp&#34; alt=&#34;A conference room with around 30 people visible watching a speaker talk, in front of a TPRC banner.&#34;&gt;
Next Generation of Perl&lt;/p&gt;
&lt;!--Photo by Andrew Baerg, 2024.---&gt;
&lt;p&gt;I attended &lt;a href=&#34;https://tprc.us/tprc-2024-las/&#34;&gt;The Perl and Raku Conference&lt;/a&gt; in Las Vegas, NV, which took place June 25–28, 2024. It was HOT outside (over 40 °C/110 °F) but we stayed cool inside at the Alexis Park Resort.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://curtispoe.org/&#34;&gt;Curtis Poe (Ovid)&lt;/a&gt; got things started with the keynote encouraging us to &lt;a href=&#34;https://www.youtube.com/watch?v=22-7yP0inu8&#34;&gt;Party Like It&amp;rsquo;s 19100+e^iπ&lt;/a&gt;, and reminded us that Vegas is lexically scoped (what happens in Vegas stays in Vegas)! More importantly he reminded us that Perl is about people, not just the technology. The Perl community has been &lt;a href=&#34;https://bit.ly/perl-events&#34;&gt;meeting all over the world&lt;/a&gt; since 1999, with this being the 25th anniversary of the first The Perl Conference (aka YAPC::NA).&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/08/the-perl-and-raku-conference-2024/ovid-keynote.webp&#34; alt=&#34;A man with a beard presents on a small stage.&#34;&gt;
Ovid Keynote&lt;/p&gt;
&lt;!--Photo by Andrew Baerg, 2024.---&gt;
&lt;p&gt;Meeting in person with people who you interact with primarily through digital channels, code commits, and &lt;a href=&#34;https://metacpan.org&#34;&gt;MetaCPAN&lt;/a&gt; documentation really highlighted the importance of the community. On the first day, I messed up timezones, showed up an hour before registration opened, and witnessed the conference organizers and core members arrive and greet each other with hugs. I also enjoyed visiting with one of the very welcoming board members of &lt;a href=&#34;https://www.perlfoundation.org/&#34;&gt;The Perl and Raku Foundation&lt;/a&gt; (TPRF).&lt;/p&gt;
&lt;p&gt;Many of the speakers and attendees put a &amp;ldquo;Hallway++&amp;rdquo; sticker on their badge which simply meant &amp;ldquo;talk to me, I&amp;rsquo;m here to meet and get to know people&amp;rdquo;. At breakfast one morning, I had the privilege of sitting with &lt;a href=&#34;https://cromedome.net/&#34;&gt;Jason Crome&lt;/a&gt;, the core maintainer of &lt;a href=&#34;https://perldancer.org&#34;&gt;Dancer&lt;/a&gt;, a framework that I have used extensively. It was amazing to be able to pick the brain of one of the people who has intimate knowledge of the software.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/08/the-perl-and-raku-conference-2024/dancing-with-cromedome.webp&#34; alt=&#34;Two men sit at a table in front of their computers, one shows the other something and has a slight smile&#34;&gt;
Dancing with Cromedome&lt;/p&gt;
&lt;!--Photo by Andrew Baerg, 2024.---&gt;
&lt;p&gt;The Perl community is large and diverse, which is reflected in the &lt;a href=&#34;https://perlcommunity.org/science/&#34;&gt;Science Perl Committee&lt;/a&gt; and the all-Perl &lt;a href=&#34;https://koha-community.org/&#34;&gt;Koha Library Software&lt;/a&gt; in use at over 4,000 libraries and with its own annual conference. It was cool to hear about the &lt;a href=&#34;https://leejo.github.io/acme-glue-talk/presentation.html#1&#34;&gt;Glue Photo Project&lt;/a&gt;, making &lt;a href=&#34;https://github.com/ology/Perl-Algorithmic-Music-2024&#34;&gt;algorithmic music&lt;/a&gt;, and gaming with the &lt;a href=&#34;https://youtu.be/7wTmA4xm6i4&#34;&gt;TinyNES&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Every community will experience conflict and this one is no different. The impact of &lt;a href=&#34;https://youtu.be/Q1H9yKf8BI0&#34;&gt;Sawyer&amp;rsquo;s resignation at TPRC 2023&lt;/a&gt; could be felt at this conference, and in response the community is focused on making things better with a &lt;a href=&#34;https://news.perlfoundation.org/post/new-standaards-of-conduct&#34;&gt;new standards of conduct&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It wouldn&amp;rsquo;t be a conference in 2024 without talk of AI. We had some &lt;a href=&#34;https://youtu.be/y3llSkCJnWk&#34;&gt;Musings on Generative AI&lt;/a&gt; and an introduction to &lt;a href=&#34;https://youtu.be/Agw6E1omIvY&#34;&gt;PerlGPT, A Code Llama LLM Fine-Tuned For Perl&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There were, of course, a lot of talks about actual Perl code!&lt;/p&gt;
&lt;p&gt;One of the things I enjoy about attending conferences is discovering things that I wasn&amp;rsquo;t looking for. &lt;a href=&#34;https://blogs.perl.org/users/chad_exodist_granum/&#34;&gt;Chad Granum&lt;/a&gt; gave a lightning talk on &lt;a href=&#34;https://metacpan.org/pod/goto::file&#34;&gt;goto::file&lt;/a&gt; and I noticed the use of &lt;a href=&#34;https://perldoc.perl.org/perlsyn#Plain-Old-Comments-%28Not!%29&#34;&gt;line directives&lt;/a&gt; which can be extremely helpful in debugging &lt;code&gt;eval&lt;/code&gt;ed code. For example, let&amp;rsquo;s say you are &lt;code&gt;eval&lt;/code&gt;ing subs into a hashref and then calling them like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$sub1&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;sub {\n  print &amp;#39;foo&amp;#39;;\n  print &amp;#39;bar&amp;#39;;\n print &amp;#39;baz&amp;#39;;\n}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$sub2&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;sub {\n  print &amp;#39;foo&amp;#39;;\n  print &amp;#39;bar&amp;#39;;\n warn &amp;#39;baz&amp;#39;;\n}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$Sub&lt;/span&gt; = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  sub1 =&amp;gt; &lt;span style=&#34;color:#038&#34;&gt;eval&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$sub1&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  sub2 =&amp;gt; &lt;span style=&#34;color:#038&#34;&gt;eval&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$sub2&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$Sub&lt;/span&gt;-&amp;gt;{sub1}-&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$Sub&lt;/span&gt;-&amp;gt;{sub2}-&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Any warn or die output will give you a line number, but no context as to which sub it originated from:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;baz at (&lt;span style=&#34;color:#038&#34;&gt;eval&lt;/span&gt; 2) line 4.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Making use of a line directive when &lt;code&gt;eval&lt;/code&gt;ing the subs like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$sub1&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;sub {\n  print &amp;#39;foo&amp;#39;;\n  print &amp;#39;bar&amp;#39;;\n print &amp;#39;baz&amp;#39;;\n}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$sub2&lt;/span&gt; = &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;sub {\n  print &amp;#39;foo&amp;#39;;\n  print &amp;#39;bar&amp;#39;;\n warn &amp;#39;baz&amp;#39;;\n}&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$Sub&lt;/span&gt; = {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  sub1 =&amp;gt; &lt;span style=&#34;color:#038&#34;&gt;eval&lt;/span&gt; &lt;span style=&#34;color:#2b2;background-color:#f0fff0&#34;&gt;qq(#line 1 &amp;#34;sub1&amp;#34;\n$sub1)&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  sub2 =&amp;gt; &lt;span style=&#34;color:#038&#34;&gt;eval&lt;/span&gt; &lt;span style=&#34;color:#2b2;background-color:#f0fff0&#34;&gt;qq(#line 1 &amp;#34;sub2&amp;#34;\n$sub2)&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$Sub&lt;/span&gt;-&amp;gt;{sub1}-&amp;gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$Sub&lt;/span&gt;-&amp;gt;{sub2}-&amp;gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Will now result in a much more friendly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;baz at sub2 line 4.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And in true open source fashion, this has been turned into a &lt;a href=&#34;https://github.com/interchange/interchange/pull/150&#34;&gt;pull request for Interchange&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/08/the-perl-and-raku-conference-2024/the-sphere.webp&#34; alt=&#34;A large spherical building with a spherical screen&#34;&gt;
The Las Vegas Sphere&lt;/p&gt;
&lt;!--Photo by Andrew Baerg, 2024.---&gt;
&lt;p&gt;I always enjoy hearing from others in the community about &lt;a href=&#34;https://metacpan.org&#34;&gt;CPAN modules&lt;/a&gt; that are in their toolbox. I learned about &lt;a href=&#34;https://metacpan.org/pod/DBIx::QuickDB&#34;&gt;DBIx::QuickDB&lt;/a&gt;, which you can use to spin up a database server on the fly, which removes the need for a running database server for tests, and also enables running concurrent tests which require a database server that would otherwise conflict. Combine this with a &lt;a href=&#34;https://metacpan.org/pod/DBIx::Class&#34;&gt;DBIx::Class&lt;/a&gt; schema and &lt;a href=&#34;https://metacpan.org/pod/DBIx::Class::Fixtures&#34;&gt;DBIx::Class::Fixtures&lt;/a&gt; and you have a very nice way to run some tests against fixed data:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;DBIx::QuickDB&lt;/span&gt; PSQL_DB  =&amp;gt; {driver =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;PostgreSQL&amp;#39;&lt;/span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$dbh&lt;/span&gt; = &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;PSQL_DB&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#038&#34;&gt;connect&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$schema&lt;/span&gt; = &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Some::Schema&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#038&#34;&gt;connect&lt;/span&gt;( &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sub&lt;/span&gt; { &lt;span style=&#34;color:#369&#34;&gt;$dbh&lt;/span&gt; }, { on_connect_do =&amp;gt; [&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;...&amp;#34;&lt;/span&gt;] } );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$schema&lt;/span&gt;-&amp;gt;deploy();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;my&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;$fixtures&lt;/span&gt; = &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;DBIx::Class::Fixtures&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;({ config_dir =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;...&amp;#39;&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;$fixtures&lt;/span&gt;-&amp;gt;populate({ no_deploy =&amp;gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;, schema =&amp;gt; &lt;span style=&#34;color:#369&#34;&gt;$schema&lt;/span&gt;, directory =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;...&amp;#39;&lt;/span&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ok(&lt;span style=&#34;color:#369&#34;&gt;$schema&lt;/span&gt;-&amp;gt;resultset(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;Foo&amp;#39;&lt;/span&gt;)-&amp;gt;count &amp;gt;= &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;database populated&amp;#39;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&#34;http://damian.conway.org&#34;&gt;Damian Conway&lt;/a&gt; gave a keynote on &lt;a href=&#34;https://youtu.be/0x9LD8oOmv0&#34;&gt;The Once and Future Perl&lt;/a&gt;, showing how far Perl has come as a language and how its rich history can be leveraged into the future: &amp;ldquo;if you can envisage what you could have done better in the past, then you can probably think of ways to make the future brighter!&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;He showed off his new &lt;a href=&#34;https://metacpan.org/pod/Multi::Dispatch&#34;&gt;Multi::Dispatch&lt;/a&gt; module which you can use now to write incredibly extensible (and beautiful) code. Here it is in action with a simple Data::Dumper clone in 5 lines of code:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;v5&lt;/span&gt;&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;.26&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Multi::Dispatch&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;multi dd :before :where(VOID) (&lt;span style=&#34;color:#369&#34;&gt;@data&lt;/span&gt;)   { say &amp;amp;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;next&lt;/span&gt;::variant }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;multi dd (&lt;span style=&#34;color:#369&#34;&gt;$k&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$v&lt;/span&gt;)                       { dd(&lt;span style=&#34;color:#369&#34;&gt;$k&lt;/span&gt;) . &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39; =&amp;gt; &amp;#39;&lt;/span&gt; . dd(&lt;span style=&#34;color:#369&#34;&gt;$v&lt;/span&gt;) }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;multi dd (&lt;span style=&#34;color:#369&#34;&gt;$data&lt;/span&gt; :where(ARRAY))          { &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;[&amp;#39;&lt;/span&gt; . &lt;span style=&#34;color:#038&#34;&gt;join&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;, &amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#038&#34;&gt;map&lt;/span&gt; {dd(&lt;span style=&#34;color:#369&#34;&gt;$_&lt;/span&gt;)}                 &lt;span style=&#34;color:#369&#34;&gt;@$data&lt;/span&gt;) . &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;]&amp;#39;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;multi dd (&lt;span style=&#34;color:#369&#34;&gt;$data&lt;/span&gt; :where(HASH))           { &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;{&amp;#39;&lt;/span&gt; . &lt;span style=&#34;color:#038&#34;&gt;join&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;, &amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#038&#34;&gt;map&lt;/span&gt; {dd(&lt;span style=&#34;color:#369&#34;&gt;$_&lt;/span&gt;, &lt;span style=&#34;color:#369&#34;&gt;$data&lt;/span&gt;-&amp;gt;{&lt;span style=&#34;color:#369&#34;&gt;$_&lt;/span&gt;})} &lt;span style=&#34;color:#038&#34;&gt;keys&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;%$data&lt;/span&gt;) . &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;}&amp;#39;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;multi dd (&lt;span style=&#34;color:#369&#34;&gt;$data&lt;/span&gt;)                        { &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;#34;&amp;#39;&lt;/span&gt; . &lt;span style=&#34;color:#038&#34;&gt;quotemeta&lt;/span&gt;(&lt;span style=&#34;color:#369&#34;&gt;$data&lt;/span&gt;) . &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;&amp;#34;&amp;#39;&lt;/span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;say dd [&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;, { bar =&amp;gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;baz&amp;#34;&lt;/span&gt; }];&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With Smartmatch (&lt;code&gt;given&lt;/code&gt;/&lt;code&gt;when&lt;/code&gt;) &lt;a href=&#34;https://perldoc.perl.org/5.40.0/perldeprecation#Smartmatch&#34;&gt;scheduled for deprecation in 5.42&lt;/a&gt; he has written a drop-in replacement that uses Multi::Dispatch: &lt;a href=&#34;https://metacpan.org/pod/Switch::Right&#34;&gt;Switch::Right&lt;/a&gt;, which addresses the issues with the original implementation that was in core.&lt;/p&gt;
&lt;p&gt;Also on display was the new &lt;a href=&#34;https://perldoc.perl.org/5.40.0/perlclass&#34;&gt;class syntax&lt;/a&gt; introduced into core in 5.38 with &lt;code&gt;use feature &#39;class&#39;&lt;/code&gt;. Here&amp;rsquo;s an example of how it looks:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-perl&#34; data-lang=&#34;perl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;v5&lt;/span&gt;&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;.40&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;use&lt;/span&gt; &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;feature&lt;/span&gt; &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;class&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;class Point {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  field &lt;span style=&#34;color:#369&#34;&gt;$x&lt;/span&gt; :param = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  field &lt;span style=&#34;color:#369&#34;&gt;$y&lt;/span&gt; :param = &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  method describe () {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      say &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;A point at ($x, $y)\n&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;   }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;Point&lt;/span&gt;-&amp;gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt;(x =&amp;gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;5&lt;/span&gt;, y =&amp;gt; &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;10&lt;/span&gt;)-&amp;gt;describe;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you are waiting for Perl 7, Damian is here to tell you that the future is now; you don&amp;rsquo;t have to wait for Perl 7 (or 17), it&amp;rsquo;s Perl 5 with Multi::Dispatch and &lt;code&gt;use feature &#39;class&#39;&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;The last day of the conference provided an opportunity to go deeper into learning the new class syntax with a workshop on building a &lt;a href=&#34;https://github.com/perigrin/going-rogue-class&#34;&gt;roguelike adventure game&lt;/a&gt; from scratch.&lt;/p&gt;
&lt;p&gt;So what&amp;rsquo;s next? The &lt;a href=&#34;https://act.yapc.eu/lpw2024/&#34;&gt;London Perl &amp;amp; Raku Workshop&lt;/a&gt; is taking place on October 26, 2024 and Perl 5.42 is just around the corner!&lt;/p&gt;
&lt;p&gt;—JAPH (Just Another Perl Hacker)&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>An OpenStreetMap Editor for Adding Public Transit Data Using GTFS</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2024/05/an-openstreetmap-editor-for-incorporating-gtfs-data/"/>
      <id>https://www.endpointdev.com/blog/2024/05/an-openstreetmap-editor-for-incorporating-gtfs-data/</id>
      <published>2024-05-08T00:00:00+00:00</published>
      <author>
        <name>Dmitry Kiselev</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2024/05/an-openstreetmap-editor-for-incorporating-gtfs-data/app-overview.webp&#34; alt=&#34;The OpenStreetMap GTFS editor. The right side is a map with a darkened map, and blue, red, and black dots overlayed along the roads where public transit stops are. In the corner, the map reads &amp;ldquo;Leaflet | copyright OpenStreetMap&amp;rdquo;. It has controls to zoom, change between satellite and OpenStreetMap, darken the map, and open in JOSM. The left side has navigation buttons reading &amp;ldquo;Import&amp;rdquo; (which is selected), &amp;ldquo;Stops&amp;rdquo;, &amp;ldquo;Routes&amp;rdquo;, &amp;ldquo;Trips&amp;rdquo;, and &amp;ldquo;Changes&amp;rdquo;. Below there is text on several lines: &amp;ldquo;gtfs.zip. Loaded 5246 stops&amp;rdquo; then a button reading &amp;ldquo;Query OSM data&amp;rdquo;. Then, &amp;ldquo;OSM Tag with GTFS stop code: &amp;quot; and a text box with &amp;ldquo;gtfs:ref&amp;rdquo; typed inside. Next, bold text reading &amp;ldquo;Possible GTFS stop code tags&amp;rdquo;, followed by a list of tags: &amp;ldquo;ref (6615 objects), route_ref (129 objects), local_ref (127 objects), ref:US-UT:uubus (24 objects), railway:ref (5 objects), loc_ref (7 objects), gtfs:stop_id (2 objects), ref:left (2 objects), ref:right (2 objects), noref (26 objects), crossing_ref (16 objects)&amp;rdquo;. Next, &amp;ldquo;show help&amp;rdquo;. &amp;ldquo;Match stops by name: &amp;quot; and an unchecked checkbox. Next, &amp;ldquo;Match stops by GTFS code in name: &amp;quot; and an unchecked checkbox. Next, bold text reading &amp;ldquo;Template tags for platform&amp;rdquo;. Then, 3 rows in a table. The first two have minus sign buttons on the left, and two columns reading: &amp;ldquo;public_transport&amp;rdquo;,&amp;ldquo;platform&amp;rdquo; and &amp;ldquo;highway&amp;rdquo;,&amp;ldquo;bus_stop&amp;rdquo;. The third row is just a plus sign button.&#34;&gt;&lt;/p&gt;
&lt;p&gt;You can think of OpenStreetMap (OSM) as Wikipedia for maps. While it may not be as well known as Google Maps or Apple Maps, OSM becomes indispensable when you need data, rather than just a visual representation on your phone. The only other real option for data retrieval is to consult a local agency, so if you&amp;rsquo;re in search of a comprehensive and global cartographic dataset, OSM is the go-to choice.&lt;/p&gt;
&lt;p&gt;OSM also excels in providing navigation for pedestrians and cyclists. For the past decade, I&amp;rsquo;ve navigated the US, Canada, and Europe using OSM through the OsmAnd app, a dedicated Android application for OpenStreetMap.&lt;/p&gt;
&lt;p&gt;Overall, my experience has been quite positive, except for one significant weakness: public transportation. Specifically, I&amp;rsquo;ve been missing the convenient access to timetables for buses, trams, or any other form of public transit.&lt;/p&gt;
&lt;p&gt;A significant part of the challenge in making public transportation data readily available on OSM and its associated applications is the fact that the OSM data model isn’t particularly well suited for this type of information. While OSM can store the location of bus and train stops, the actual timetables change so often that keeping them up to date in OSM is essentially impossible.&lt;/p&gt;
&lt;p&gt;Enter the General Transit Feed Specification (GTFS), a widely adopted standard for managing public transportation data. I wanted to explore ways to integrate OSM with GTFS effectively.&lt;/p&gt;
&lt;h3 id=&#34;my-openstreetmap-editor-for-gtfs-data&#34;&gt;My OpenStreetMap editor for GTFS data&lt;/h3&gt;
&lt;p&gt;A simple approach for addressing this challenge would be to overlay an OSM map with stops from GTFS. While this would be relatively straightforward, it doesn&amp;rsquo;t leverage OSM&amp;rsquo;s biggest strength—its vibrant community. One of OSM&amp;rsquo;s most significant advantages is that it dynamically incorporates user contributions: as soon as the community accesses useful data, members enhance the details and accuracy of its mapping data. Therefore, I’ve been working on truly integrating GTFS data with OSM, creating a cohesive mapping experience rather than settling for a disjointed overlay.&lt;/p&gt;
&lt;p&gt;Although the implementation is a bit trickier, I believe the better approach for handling this challenge involves merging GTFS stops with those already present in OSM and incorporating GTFS stop codes into OSM data. Hence, I&amp;rsquo;ve developed a single-page online editor. This tool facilitates the creation, editing, and deletion of stops within OSM. It also highlights stops based on their alignment with stops specified in a given GTFS file.&lt;/p&gt;
&lt;p&gt;Editing the routes wasn’t a priority, yet I found it necessary to match GTFS routes and trips with OSM routes and their variants. Displaying trips shows which part of a street a route uses and also which side of a street bus stops are located on.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve deployed the app on my GitHub, where you can try it out: &lt;a href=&#34;https://kiselev-dv.github.io/osm-gtfs/&#34;&gt;https://kiselev-dv.github.io/osm-gtfs/&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;how-to-use-the-app&#34;&gt;How to use the app&lt;/h3&gt;
&lt;p&gt;To start, download a GTFS data dump. They are usually published by local transit companies. As an example, I&amp;rsquo;ll use data from Halifax, Nova Scotia.&lt;/p&gt;
&lt;p&gt;Open the GTFS ZIP file in the editor. The editor should parse stops from GTFS.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/05/an-openstreetmap-editor-for-incorporating-gtfs-data/gtfs-file.webp&#34; alt=&#34;The OSM editing app. The map is not darkened and does not have any blue, red, or black dots. The left bar does not have a list of tags anymore.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Next, query OSM data and select “OSM Tag with GTFS stop code.” After OSM data is loaded you will get OSM tags statistics which might help you to see if there are some popular tag combination for GTFS data in the area. OSM doesn’t have strict data model, but as a rule of thumb for that type of data &lt;code&gt;ref&lt;/code&gt; or &lt;code&gt;gtfs:ref&lt;/code&gt; should work. If there are multiple agencies sharing a stop and each of them publishes its own GTFS data, you can use something like &lt;code&gt;gtfs:&amp;lt;agency_name&amp;gt;:ref&lt;/code&gt;. By default the editor will substitute the most popular tag containing &lt;code&gt;ref&lt;/code&gt; or &lt;code&gt;gtfs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now you can match stops from OSM with stops from GTFS. Matches are color coded:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Blue: matching stops&lt;/li&gt;
&lt;li&gt;Red: stops which are present in GTFS but not mapped in OSM (stops to add)&lt;/li&gt;
&lt;li&gt;Black: stops mapped in OSM but not matched with GTFS stop (probably outdated stops which are no longer in use and may be deleted, or stops not used by the agency whose GTFS data you are using)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/05/an-openstreetmap-editor-for-incorporating-gtfs-data/stop-matching.webp&#34; alt=&#34;The OSM editing app. The &amp;ldquo;Stops&amp;rdquo; navigation option is selected. On the map, there are black dots on top of bus stops visible in the actual map texture. Most of these black dots have nearby red circles. There are a couple of blue dots on top of bus stops. The text in the left app bar reads: &amp;ldquo;Filter matches by Route/Trip&amp;rdquo; then a dropdown select box reading &amp;ldquo;Select a route&amp;rdquo;. Then, &amp;ldquo;Show matched: 4844&amp;rdquo; with a checked checkbox. Then, &amp;ldquo;Show unmatched GTFS: 402&amp;rdquo; with a checked checkbox. Then, &amp;ldquo;Show unmatched OSM: 1512&amp;rdquo; with a checked checkbox. Then a button, &amp;ldquo;Open listed stops in JOSM&amp;rdquo;. Then, a list of bus stops: &amp;ldquo;Constitution Blvd @ 3700 S&amp;rdquo; with a blue circle, &amp;ldquo;Constitution Blvd @ 3601 S&amp;rdquo; with a blue circle, etc.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;adding-stops-to-osm&#34;&gt;Adding stops to OSM&lt;/h3&gt;
&lt;p&gt;There are two main cases for adding or matching a stop:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is no stop in OSM at all and we want to create a new one&lt;/li&gt;
&lt;li&gt;There is a stop in OSM and we want to edit it and add GTFS data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Select one of the GTFS stops. On the left panel there will be two buttons: “Create” and “Assign”.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/05/an-openstreetmap-editor-for-incorporating-gtfs-data/create-assign-buttons.webp&#34; alt=&#34;The OSM editing app. The &amp;ldquo;Stops&amp;rdquo; tab is selected. In the map, there are red circles on either side of a road. One is larger than the other, indicating its selection. Text in the bar on the left reads: &amp;ldquo;900 E / 300 S (SB)&amp;rdquo;, then &amp;ldquo;GTFS Stop code: 820054, GTFS Stop id: 21317&amp;rdquo;. Then, &amp;ldquo;Routes on this stop: Provo Central Station (831 - PROVO GRANDVIEW)&amp;rdquo; with a filter icon and a rising arrow icon. There is another similar stop. Then &amp;ldquo;This GTFS stop has no matched OSM stop. Select one of the OSM stops nearby to set GTFS code in its tags&amp;rdquo;. Then, &amp;ldquo;OSM Stop platform:&amp;rdquo; and two buttons reading &amp;ldquo;Create&amp;rdquo; and &amp;ldquo;Assign&amp;rdquo;. These buttons are highlighted. Then there are stop filter options.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The “Create” button will switch the map into satellite mode, create a new OSM stop with GTFS code, and add a stop name at the point where you click. When creating a stop or editing its location, pay attention to which side of the road it should be on. To simplify that, you can highlight one of the routes in the “Routes on this stop” section.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/05/an-openstreetmap-editor-for-incorporating-gtfs-data/create-stop.webp&#34; alt=&#34;The OSM editing app. The map is showing satellite imagery. A blue stop is selected, with options on the left to move or reassign the stop, as well as OSM data: &amp;ldquo;name: 900 E / 300 S (SB), ref: 820054, public_transport: platform, highway: bus_stop&amp;rdquo;. This data is in a two-column editable table.&#34;&gt;&lt;/p&gt;
&lt;p&gt;The “Assign” button allows you to select one of the existing OSM stops, and set its GTFS code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2024/05/an-openstreetmap-editor-for-incorporating-gtfs-data/assign-stop.webp&#34; alt=&#34;The OSM editing app. One blue stop is highlighted. It has more OSM data filled out, including &amp;ldquo;network: UTA, network:wikidata: Q7902494&amp;rdquo;, etc.&#34;&gt;&lt;/p&gt;
&lt;h3 id=&#34;demo-videos&#34;&gt;Demo videos&lt;/h3&gt;
&lt;p&gt;You can see a &lt;a href=&#34;https://www.youtube.com/watch?v=fG_RvC7AWfk&#34;&gt;video on my YouTube channel&lt;/a&gt; with the latest updates to the editor, along with a live editing session.&lt;/p&gt;
&lt;p&gt;I have also created a &lt;a href=&#34;https://www.youtube.com/playlist?list=PL0eKSR1VCpIQiTntglaVhKXt4PKWTcEg6&#34;&gt;YouTube playlist&lt;/a&gt; with more demos.&lt;/p&gt;
&lt;h3 id=&#34;an-important-note&#34;&gt;An important note&lt;/h3&gt;
&lt;p&gt;An important note is that I do not upload results directly into OSM. Instead, I generate an XML file for an OSM changeset which you can view and edit in JOSM or another editor. I do this partly so I do not have to deal with authentication, but more importantly, because I see editing public transport relations in OSM as an advanced topic and it makes sense for editors to demonstrate a higher level of sophistication than general users. But I might change my mind about this if I get feedback that it would be desirable.&lt;/p&gt;
&lt;p&gt;My app is built using React and Leaflet. You can find the code on my GitHub: &lt;a href=&#34;https://github.com/kiselev-dv/osm-gtfs&#34;&gt;https://github.com/kiselev-dv/osm-gtfs&lt;/a&gt;&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Cesium Labels</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2023/05/cesium-labels/"/>
      <id>https://www.endpointdev.com/blog/2023/05/cesium-labels/</id>
      <published>2023-05-24T00:00:00+00:00</published>
      <author>
        <name>Dmitry Kiselev</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2023/05/cesium-labels/rome-map.webp&#34; alt=&#34;A 16th-century topographical map of ancient Rome. Buildings are drawn in simple, clear, engraved lines. Streets and important structures like the Pantheon are labeled in Latin.&#34;&gt;&lt;/p&gt;
&lt;!-- Image: Topographical Map of Ancient Rome by Nicolas Beatrizet, 1557. Public domain, acquired from https://www.nga.gov/collection/art-object-page.112707.html --&gt;
&lt;p&gt;Improving place labels in CesiumJS, the open source JavaScript library for 3D globes and maps, has been a longstanding request from some of our VisionPort clients. The display of labels embedded in imagery has not been up to their expectations. The names look upside down as the globe is rotated and there&amp;rsquo;s no option to change the language:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2023/05/cesium-labels/cesium-old-labels.webp&#34; alt=&#34;The old Cesium labels. The 3D camera is rotated so that the labels reading &amp;ldquo;New York&amp;rdquo;, &amp;ldquo;Kips Bay&amp;rdquo;, etc., are at a 90 degree angle, making it difficult to read.&#34;&gt;&lt;/p&gt;
&lt;p&gt;Cesium has vector labels which allow you to anchor some text to a point on a map which will always be aligned with the camera. However, there is no ready-to-use solution to display city names in particular and no way to load them according to a specific zoom level.&lt;/p&gt;
&lt;p&gt;To improve performance when displaying labels in Cesium it would make sense to load labels as a tile tree and only show some of the top of the tree at different zoom levels in a manner similar to how &lt;a href=&#34;https://developers.google.com/kml/documentation/regions?hl=en#smart-loading-of-region-based-network-links&#34;&gt;KML Regions with NetworkLinks&lt;/a&gt; work. Unfortunately, although Cesium supports KML, it doesn’t support KML Regions and NetworkLinks updates on Region change.&lt;/p&gt;
&lt;p&gt;Another off-the-shelf solution that might conceivably work would be to use Cesium 3D tiles, but unfortunately, tiles do not support 2D Billboards.&lt;/p&gt;
&lt;p&gt;Calculating regions and their level of detail is complicated, but Cesium already does most of that work for us. Cesium already calculates visible tiles and their corresponding levels of detail for ImageryProviders. An ImageryProvider is supposed to load imagery for a given tile’s coordinates and zoom level, which is almost what we want, except that we want to render some 3D primitives for labels, not 2D images for the earth surface.&lt;/p&gt;
&lt;p&gt;We can read what tiles are going to be rendered by Cesium and store them in a variable:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;const&lt;/span&gt; tilesToRender = viewer.scene.globe._surface._tilesToRender;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, we get the &lt;a href=&#34;https://wiki.openstreetmap.org/wiki/TMS&#34;&gt;TMS&lt;/a&gt; coordinates out of the visible tiles on screen. Then we calculate the difference between the currently visible tiles and the new ones. We grab all the new tiles that we need from that list, and then we create the &lt;a href=&#34;https://cesium.com/learn/cesiumjs/ref-doc/Entity.html&#34;&gt;Entities&lt;/a&gt; with labels and some other styling properties.&lt;/p&gt;
&lt;h3 id=&#34;backend-and-data-source&#34;&gt;Backend and data source&lt;/h3&gt;
&lt;p&gt;To source the labels we use &lt;a href=&#34;https://www.geonames.org/&#34;&gt;GeoNames&lt;/a&gt;. They have a nice dataset for cities with their population included in the data. We take the city labels they provide and store them into a quadtree. Each node in the quadtree has 10 labels associated with it, starting with the largest population at the top of the data structure and going down. Based on the altitude we traverse a certain distance down the quadtree returning all cities from each node that corresponds to the tile requested.&lt;/p&gt;
&lt;p&gt;Here is a video demonstrating how labels display with this new feature we’ve developed:&lt;/p&gt;
&lt;p&gt;&lt;video type=&#34;video/mp4&#34; controls src=&#34;/blog/2023/05/cesium-labels/cesium-new-labels.mp4&#34; style=&#34;max-height:30rem;width:auto&#34;&gt;&lt;/video&gt;&lt;/p&gt;
&lt;h3 id=&#34;future-plans&#34;&gt;Future Plans&lt;/h3&gt;
&lt;p&gt;Currently, the entity creation is done on the frontend, but we are planning to move this to the backend sometime soon. There is also some crowding of the labels when zooming out since surface tiles aren&amp;rsquo;t immediately updated. The tiles can likely be filtered to remove unneeded ones more aggressively.&lt;/p&gt;
&lt;h3 id=&#34;running-on-your-system&#34;&gt;Running On Your System&lt;/h3&gt;
&lt;p&gt;We have released this code &lt;a href=&#34;https://github.com/EndPointCorp/tiled-city-labels&#34;&gt;on our GitHub&lt;/a&gt; under the Apache license for everyone to use.&lt;/p&gt;
&lt;p&gt;To get this up and running on your local system, start with a Git clone:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git clone https://github.com/EndPointCorp/tiled-city-labels.git&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then within the repo there’s a README to follow. We will give instructions here, but if they differ from the README follow those instead:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;cd&lt;/span&gt; tiled-city-labels
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker build -t cesium-labels .
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker run -p 48088:48088 --rm cesium-labels&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then in a new terminal window still inside the project:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#038&#34;&gt;cd&lt;/span&gt; demo/
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;python -m SimpleHTTPServer &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;8000&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now you can navigate to http://localhost:8000/ in your browser and see the city labels up and running. To customize this to your own system, you can use the mixin &lt;code&gt;demo/js/CitiesDataSource.js&lt;/code&gt; and add that to your own project. You can also change the port/server used in the mixin by editing the &lt;code&gt;fetch&lt;/code&gt; command inside the &lt;code&gt;queryData&lt;/code&gt; function.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Formatting SQL code with pgFormatter within Vim</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/04/formatting-sql-vim-pgformat/"/>
      <id>https://www.endpointdev.com/blog/2022/04/formatting-sql-vim-pgformat/</id>
      <published>2022-04-26T00:00:00+00:00</published>
      <author>
        <name>Josh Tolley</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/04/formatting-sql-vim-pgformat/20220411_091408.webp&#34; alt=&#34;Outdoor view of a creek bank with dry trees and old wooden buildings against a blue sky&#34;&gt;
Photo by Garrett Skinner&lt;/p&gt;
&lt;p&gt;Sometimes a little, seemingly simple tip can make a world of difference. I&amp;rsquo;ve got enough gray hair these days that it would be pretty easy for me to start thinking I&amp;rsquo;d seen an awful lot, yet quite frequently when I watch a colleague working in a meeting or a &lt;a href=&#34;https://github.com/tmux/tmux&#34;&gt;tmux&lt;/a&gt; session or somewhere, I learn some new and simple thing that makes my life demonstrably easier.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://fluca1978.github.io/2022/04/13/EmacsPgFormatter.html&#34;&gt;Luca Ferrari recently authored a post&lt;/a&gt; about using pgFormatter in Emacs; essentially the same thing works in Vim, my editor of choice, and it&amp;rsquo;s one of my favorite quick tips when working with complicated queries. I don&amp;rsquo;t especially want to get involved an editor war, and offer the following only in the spirit of friendly cooperation for the Vim users out there.&lt;/p&gt;
&lt;p&gt;As Luca mentioned, &lt;a href=&#34;https://github.com/darold/pgFormatter&#34;&gt;pgFormatter&lt;/a&gt; is a convenient way to make SQL queries readable, automatically. It&amp;rsquo;s easy enough to feed it some SQL, and get a nice-looking result as output:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#a61717;background-color:#e3d2d2&#34;&gt;$&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;pg_format&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&amp;lt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;create_outbreaks.&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;sql&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;INSERT&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;INTO&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;outbreak&lt;span style=&#34;color:#bbb&#34;&gt;                      
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;SELECT&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;                              
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;nextval(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;outbreak_id&amp;#39;&lt;/span&gt;::regclass),&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;extract&lt;/span&gt;(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;year&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;FROM&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;now())::&lt;span style=&#34;color:#038&#34;&gt;text&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;||&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;||&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;nextval(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;outbreak_number_seq&amp;#39;&lt;/span&gt;)::&lt;span style=&#34;color:#038&#34;&gt;text&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#888&#34;&gt;--number
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;                        
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;SELECT&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;            &lt;/span&gt;first_name&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;FROM&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;person&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;TABLESAMPLE&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;BERNOULLI&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;10&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;LIMIT&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;1&lt;/span&gt;),&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#888&#34;&gt;-- name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;    &lt;/span&gt;NOW()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;-&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#038&#34;&gt;interval&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#39;1 day&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;*&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;random()&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;*&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;100&lt;/span&gt;,&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;SELECT&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;            &lt;/span&gt;id&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;        &lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;FROM&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;TABLESAMPLE&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;BERNOULLI&lt;span style=&#34;color:#bbb&#34;&gt; &lt;/span&gt;(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;10&lt;/span&gt;)&lt;span style=&#34;color:#bbb&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#bbb&#34;&gt;&lt;/span&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In my perfect world I might quibble with some of its formatting decisions, such as the lack of indent on the &lt;code&gt;LIMIT 1&lt;/code&gt; line above. But in practice the results are good enough for my tastes that I haven&amp;rsquo;t bothered to investigate whether I can improve them. I just use it, and it&amp;rsquo;s good enough for me.&lt;/p&gt;
&lt;p&gt;And because Vim lets me highlight a region, pipe it through an external program, and replace the region with that program&amp;rsquo;s output, it&amp;rsquo;s easy to use it simply by selecting a section of code and typing &lt;code&gt;:!pg_format&lt;/code&gt; like this:&lt;/p&gt;
&lt;img src=&#34;/blog/2022/04/formatting-sql-vim-pgformat/sample.gif&#34; width=250 height=492 alt=&#34;pgformatter example animation of terminal&#34; /&gt;

      </content>
    </entry>
  
    <entry>
      <title>On Shapefiles and PostGIS</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2022/04/shapefiles-postgis/"/>
      <id>https://www.endpointdev.com/blog/2022/04/shapefiles-postgis/</id>
      <published>2022-04-02T00:00:00+00:00</published>
      <author>
        <name>Josh Tolley</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2022/04/shapefiles-postgis/endurance-clip.webp&#34; alt=&#34;Partial map of the voyage of the Endurance, from the book &amp;ldquo;South&amp;rdquo;, Ernest H. Shackleton&#34;&gt;
Partial map of the voyage of the Endurance, from &lt;a href=&#34;https://www.gutenberg.org/ebooks/5199&#34;&gt;&amp;ldquo;South&amp;rdquo;, by Ernest Shackleton&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The shapefile format is commonly used in geospatial vector data interchange, but as it&amp;rsquo;s managed by a commercial entity, Esri, and as GIS is a fairly specialized field, and perhaps because the format specification is only &lt;a href=&#34;https://en.wikipedia.org/wiki/Shapefile&#34;&gt;&amp;ldquo;mostly open&amp;rdquo;&lt;/a&gt;, these files can sometimes be confusing to the newcomer. Perhaps these notes can help clarify things.&lt;/p&gt;
&lt;p&gt;Though the name &amp;ldquo;shapefile&amp;rdquo; would suggest a single file in filesystem parlance, a shapefile requires at least three different files, including filename extensions &lt;code&gt;.shp&lt;/code&gt;, &lt;code&gt;.shx&lt;/code&gt;, and &lt;code&gt;.dbf&lt;/code&gt;, stored in the same directory, and the term &amp;ldquo;shapefile&amp;rdquo; often refers to that directory, or to an archive such as a zipfile or tarball containing that directory.&lt;/p&gt;
&lt;h3 id=&#34;qgis&#34;&gt;QGIS&lt;/h3&gt;
&lt;p&gt;&lt;a href=&#34;https://qgis.org&#34;&gt;QGIS&lt;/a&gt; is an open-source package to create, view, and process GIS data. One good first step with any shapefile, or indeed any GIS data, is often to take a look at it. Simply tell QGIS to open the shapefile directory. It may help to add other layers, such as one of the world map layers QGIS provides by default, to see the shapefile data in context.&lt;/p&gt;
&lt;h3 id=&#34;gdal&#34;&gt;GDAL&lt;/h3&gt;
&lt;p&gt;Though QGIS can convert between GIS formats itself, I prefer working in a command-line environment. &lt;a href=&#34;https://gdal.org/&#34;&gt;The GDAL software suite&lt;/a&gt; aims to translate GIS data between many available formats, including shapefiles. I most commonly use its &lt;code&gt;ogr2ogr&lt;/code&gt; command-line utility, along with the excellent accompanying manpage.&lt;/p&gt;
&lt;p&gt;In short, a typical &lt;code&gt;ogr2ogr&lt;/code&gt; command tells the utility where to find the input data and where to put the converted output, optionally with various reformatting and processing options. You&amp;rsquo;ll find some examples below.&lt;/p&gt;
&lt;h3 id=&#34;postgis&#34;&gt;PostGIS&lt;/h3&gt;
&lt;p&gt;Much of our (ok, my) GIS work has involved &lt;a href=&#34;https://postgis.net&#34;&gt;PostGIS&lt;/a&gt;, an extension to the PostgreSQL database for handling GIS data. It&amp;rsquo;s been convenient for me to process GIS data using the same language and tools I use to process other data. It uses GDAL&amp;rsquo;s libraries internally.&lt;/p&gt;
&lt;h3 id=&#34;examples&#34;&gt;Examples&lt;/h3&gt;
&lt;h4 id=&#34;import-shapefile-data-into-postgis&#34;&gt;Import Shapefile data into PostGIS&lt;/h4&gt;
&lt;p&gt;The example below comes from a customer&amp;rsquo;s project we recently worked on. They provided us a set of several shapefiles, which I first arranged in a directory structure. This code imports each of them into a PostGIS database, in the &lt;code&gt;shapefiles&lt;/code&gt; schema.&lt;/p&gt;
&lt;p&gt;The other arguments to &lt;code&gt;ogr2ogr&lt;/code&gt; specify the output format (&amp;ldquo;PostgreSQL&amp;rdquo;), the destination database name, and the directory which stores the shapefile. &lt;code&gt;ogr2ogr&lt;/code&gt; expects the destination and source arguments in that order, as two positional arguments, so here the destination is &lt;code&gt;PG:dbname=destdb&lt;/code&gt;, and the source file name comes from the the &lt;code&gt;$i&lt;/code&gt; script variable.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;for&lt;/span&gt; i in &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`&lt;/span&gt;find . -name &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;*shp&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;`&lt;/span&gt;; &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;j&lt;/span&gt;=&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;$(&lt;/span&gt;basename &lt;span style=&#34;color:#369&#34;&gt;$i&lt;/span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#369&#34;&gt;k&lt;/span&gt;=&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;j&lt;/span&gt;/.shp/&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ogr2ogr -f PostgreSQL -nln shapefiles.&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;${&lt;/span&gt;&lt;span style=&#34;color:#369&#34;&gt;k&lt;/span&gt;&lt;span style=&#34;color:#33b;background-color:#fff0f0&#34;&gt;}&lt;/span&gt; PG:dbname=destdb &lt;span style=&#34;color:#369&#34;&gt;$i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;export-postgis-data-as-kml&#34;&gt;Export PostGIS data as KML&lt;/h4&gt;
&lt;p&gt;This example creates a KML file from PostGIS query results. The arguments provide the query to use to fetch the data, the output format (&amp;ldquo;KML&amp;rdquo;), the output file name, and the source database. This will create a KML file containing a set of unstyled placemarks, with names from the &lt;code&gt;property_code&lt;/code&gt; column, and geometry data from the &lt;code&gt;outline_geom&lt;/code&gt; column in the &lt;code&gt;properties&lt;/code&gt; table of our database.&lt;/p&gt;
&lt;p&gt;In this project, &lt;code&gt;outline_geom&lt;/code&gt; contained GIS &amp;ldquo;linestrings&amp;rdquo;, data types consisting of a series of lines, which &lt;code&gt;ogr2ogr&lt;/code&gt; translated into KML polygons. Had &lt;code&gt;outline_geom&lt;/code&gt; contained points, for instance, the KML result would also have been points. In other words, &lt;code&gt;ogr2ogr&lt;/code&gt; automatically chooses the correct KML object type based on the GIS object type in the input data.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ogr2ogr -sql &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;select property_code, outline_geom from properties&amp;#34;&lt;/span&gt; -f KML outlines.kml PG:dbname=properties&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note that though the examples above use PostGIS, &lt;code&gt;ogr2ogr&lt;/code&gt; can take shapefile input and produce KML output directly without the PostGIS intermediary. We used PostGIS in these cases for other purposes, such as to filter the output and limit the attributes stored in the KML result.&lt;/p&gt;
&lt;p&gt;By default, &lt;code&gt;ogr2ogr&lt;/code&gt; puts all the attributes from the shapefile into &lt;code&gt;ExtendedData&lt;/code&gt; elements in the KML, but in our case we didn&amp;rsquo;t want those. We also didn&amp;rsquo;t want all the entries in the shapefile in our resulting KML. To skip the PostGIS step, we might do something like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ogr2ogr -f kml output.kml shapefile_directory/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What tools do you use for shapefile processing? Please let us know!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>CZML-KML Editor Geometry Editing</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2021/04/czml-kml-editor-geometry-editing/"/>
      <id>https://www.endpointdev.com/blog/2021/04/czml-kml-editor-geometry-editing/</id>
      <published>2021-04-13T00:00:00+00:00</published>
      <author>
        <name>Dmitry Kiselev</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2021/04/czml-kml-editor-geometry-editing/czml-kml-geometry-editing.jpg&#34; alt=&#34;Screenshot of CZML-KML editor showing geometry editing feature&#34;&gt;&lt;/p&gt;
&lt;p&gt;We are happy to introduce a new feature for the Cesium CZML-KML Editor: polygons and polylines geometry editing. You can now edit geometries for existing entities and move entered points during the creation process. Here is a video with a short summary of the editing process:&lt;/p&gt;
&lt;iframe style=&#34;margin-bottom: 1em&#34; width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube-nocookie.com/embed/rLhy35_X5iA&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture&#34; allowfullscreen&gt;&lt;/iframe&gt;
&lt;p&gt;See our previous blog post &lt;a href=&#34;/blog/2020/12/cesium-kml-czml-editor/&#34;&gt;introducing the Cesium CZML-KML Editor&lt;/a&gt; for further reference.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Announcing the Cesium KML-CZML Editor</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/12/cesium-kml-czml-editor/"/>
      <id>https://www.endpointdev.com/blog/2020/12/cesium-kml-czml-editor/</id>
      <published>2020-12-21T00:00:00+00:00</published>
      <author>
        <name>Dmitry Kiselev</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-00.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;End Point’s immersive technology team is happy to present a great new tool for the rapidly growing Cesium community: &lt;a href=&#34;https://www.visionport.com/cesium-kml-czml-editor/&#34;&gt;Cesium KML-CZML Editor&lt;/a&gt;. The editor gives users the ability to visually and dynamically edit KML and CZML in its Cesium browser window. Updates made with it can be exported at any time to CZML, the native markup language for Cesium.&lt;/p&gt;
&lt;p&gt;The Cesium KML-CZML Editor addresses an important but hitherto unaddressed need of the Cesium community: It provides an intuitive interface for making adjustments to fix the many inconsistencies with how KML created for (and often by) Google Earth appears on 3D maps rendered with Cesium. It is a powerful tool for converting and adapting KML for Google Earth into CZML that displays nicely in Cesium. The editor also works as a visual editor for creating and editing CZML, regardless of whether you’re converting from KML.&lt;/p&gt;
&lt;p&gt;The inconsistencies with how Cesium displays KML created for Google Earth are due to occasional differences between how Cesium and Google Earth render KML when various attributes aren’t specifically set within a given instance of code. The situation is similar to how web browsers sometimes interpret given instances of HTML differently. Just as with HTML, KML doesn’t require every attribute to be defined in a given instance of markup code.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-01.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;On the left side we have the editor toolbar, and on the right side the Cesium globe showing loaded or created entities the way they would appear in Cesium.&lt;/p&gt;
&lt;p&gt;The Editor Toolbar consist of the following areas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;File Import: you can load KML, KMZ, CZML and GeoJSON formatted data, and you can load multiple files to combine them into one CZML document.&lt;/li&gt;
&lt;li&gt;Creation tools.&lt;/li&gt;
&lt;li&gt;A list of uploaded or created entities (will appear after upload or creation of a new entity).&lt;/li&gt;
&lt;li&gt;The actual editor for entity properties (will appear after entity selection).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let’s start with a very basic example of highlighting a building by marking it with a pin in Google Earth:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-02.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now let’s move the camera about in different ways, especially noticing how the pin appears when zooming in and out. The pin stays close to the top of the building.&lt;/p&gt;
&lt;video controls&gt;
  &lt;source src=&#34;/blog/2020/12/cesium-kml-czml-editor/video-0.webm&#34; type=&#34;video/webm&#34;&gt;
  Sorry, your browser doesn’t support embedded videos.
&lt;/video&gt;
&lt;p&gt;Then let’s open the same KML in Cesium with a 3D building tileset loaded, to compare apples to apples:&lt;/p&gt;
&lt;video controls&gt;
  &lt;source src=&#34;/blog/2020/12/cesium-kml-czml-editor/video-1.webm&#34; type=&#34;video/webm&#34;&gt;
  Sorry, your browser doesn’t support embedded videos.
&lt;/video&gt;
&lt;p&gt;It’s easy to see that the green frame which is placed around the geographic position stays around the building, but the icon point of rotation in Cesium doesn’t match the one in Google Earth and the pin falls through the roof and lifts up unfortunately high above the building.&lt;/p&gt;
&lt;p&gt;KML doesn’t enforce certain aspects of its representation. If certain attributes are not specified or can’t be implemented exactly, then the KML browsers have the freedom to set different default values for them.&lt;/p&gt;
&lt;p&gt;In this example, Cesium and Google Earth display different pixel offsets for the pin icon, because in KML the offset is specified as a ratio of the image, but Cesium specifies the offset in pixels. Cesium converts the pixel offset of the pin from the ratio specified, but with some errors. Cesium reverses the X axis, and in some cases Cesium also calculates the pin icon image size incorrectly. So here it calculates the X offset as -4, which moves the pin icon to the left. To fix that, we can reverse the X axis back to 4 or even move the pin icon 8 pixels further to the right.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-03.jpg&#34; alt=&#34;&#34;&gt;
🡆
&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-04.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;The next issue is how the Z coordinate (altitude) is interpreted. Is it above sea level, above land according to some averaged model of the land, or on the land directly below a point?&lt;/p&gt;
&lt;p&gt;Note that 3D tilesets in Cesium are absolutely positioned. This means that if you show 3D buildings and want your pin to stick to a building, it’s better to use absolute elevation. Hence, it makes sense to set Height Reference to &lt;code&gt;NONE&lt;/code&gt;, which will force Cesium to use the absolute pin position.&lt;/p&gt;
&lt;p&gt;Another option in cases like this is to use relative elevation. This can be helpful if you don’t use 3D tiles, but instead use 3D terrain data, for example, with Cesium World Terrain or Maptiler. It’s especially helpful if you will be switching 3D terrain on and off. In this case, a relatively positioned pin will follow the elevation regardless of whether 3D terrain is turned on or off.&lt;/p&gt;
&lt;p&gt;To set it up, adjust Altitude and set Height Reference to &lt;code&gt;RELATIVE_TO_GROUND&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-05.jpg&#34; alt=&#34;&#34;&gt;
&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-06.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here are the results for both options:&lt;/p&gt;
&lt;video controls&gt;
  &lt;source src=&#34;/blog/2020/12/cesium-kml-czml-editor/video-2.webm&#34; type=&#34;video/webm&#34;&gt;
  Sorry, your browser doesn’t support embedded videos.
&lt;/video&gt;
&lt;p&gt;As you can see, now we have the pin-point of the marker stuck to the desired location.&lt;/p&gt;
&lt;p&gt;These are the basics for how to match pins created in Google Earth with their Cesium representations, but in Cesium there are many more options for rendering pins. You can read the &lt;a href=&#34;https://cesium.com/docs/cesiumjs-ref-doc/BillboardGraphics.html&#34;&gt;relevant Cesium documentation&lt;/a&gt; (in Cesium terminology Pins are called Billboards) or interactively explore a live version of this editor to change color, rotation, translucency by distance, and other properties.&lt;/p&gt;
&lt;p&gt;Now let’s look at a polygon created in KML for Google Earth:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-07.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Then let’s open it in Cesium:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-08.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Well, we can select a polygon, but we don’t see it. This happens because the 3D terrain data covers the polygon. Just changing the Height reference helps, but it’s still not what we want:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-09.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Cesium has some default assumptions about polygons and rectangles and when to clamp them to the ground.&lt;/p&gt;
&lt;p&gt;As commented in the &lt;a href=&#34;https://sandcastle.cesium.com/?src=Clamp%20to%20Terrain.html&#34;&gt;Cesium SandCastle Clamp to Terrain demo code&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Corridors, polygons and rectangles will be clamped automatically if they are filled with a constant color and have no height or extruded height.&lt;/p&gt;
&lt;p&gt;NOTE: Setting height to 0 will disable clamping.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;So to get a polygon actually clamped to ground, we have to delete the Height property, not just set it to 0:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-10.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now let’s add a 3D tileset:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-11.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;And again, this is probably not exactly what you wanted. By default, Cesium doesn’t know what type of 3D tileset you would prefer, or whether you want the polygons to cover the tiles, or the tiles to cover the polygons. If you have a fairly large polygon covering a whole city, and a fairly low resolution 3D tileset, you probably want to highlight the whole area. But in the example above, we want our polygon to stick to the ground but not the buildings.&lt;/p&gt;
&lt;p&gt;Next, let’s use the Classification Type field, setting it to “TERRAIN” to select what the clamped polygon applies to:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-12.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Now, let’s open some exported data. Here’s a typical set of exported points generated with geojson.io:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-13.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Let’s choose a point and change its icon and its icon’s vertical alignment (so it doesn’t fall through the terrain) and dimensions:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-14.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Here we have selected one icon and updated its properties to make it appear as we want, but changing all the icons one by one would take forever, especially for large data sets.&lt;/p&gt;
&lt;p&gt;Click “COPY TO OTHER PINS”:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-15.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;On the left is a list with all the entities to which the changed properties are applicable and on the right is the list of the properties that were changed for the model entity selected. By default, all the properties are checked to be copied to applicable entities but in this example we only want to change the vertical alignment for all the pins, but want to keep the icon and icon dimension of the other pins unchanged. So we uncheck the image, width and height properties:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-16.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;Below we see how our scene looks after the update is applied. All the pins are above the ground, but the icons and dimensions are not changed:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2020/12/cesium-kml-czml-editor/image-17.jpg&#34; alt=&#34;&#34;&gt;&lt;/p&gt;
&lt;p&gt;After you have all the data styled the way you want, download your CZML!&lt;/p&gt;
&lt;p&gt;The Cesium KML-CZML Editor code is Apache 2 licensed. It is &lt;a href=&#34;https://github.com/EndPointCorp/cesium-kml-czml-editor&#34;&gt;available here&lt;/a&gt;.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>EMSA: Electronic Messaging Staging Area</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2020/10/electronic-messaging-staging-area/"/>
      <id>https://www.endpointdev.com/blog/2020/10/electronic-messaging-staging-area/</id>
      <published>2020-10-30T00:00:00+00:00</published>
      <author>
        <name>Elizabeth Garrett Christensen</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2020/10/electronic-messaging-staging-area/emsa-banner.jpg&#34; alt=&#34;Sunset&#34;&gt;
&lt;a href=&#34;https://flic.kr/p/pnRYaf&#34;&gt;Photo&lt;/a&gt; by &lt;a href=&#34;https://flic.kr/ps/2bXFPr&#34;&gt;Lee Roberts&lt;/a&gt;, &lt;a href=&#34;https://creativecommons.org/licenses/by-sa/2.0/&#34;&gt;CC BY-SA 2.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;End Point has been involved with developing disease surveillance software for over a decade. One component of that work is EMSA, short for “Electronic Messaging Staging Area”.&lt;/p&gt;
&lt;p&gt;The EMSA system we’re currently using was developed by a consortium of states led by Utah, Kansas, and Nevada. It is a PHP web application and can be hosted in the cloud or on-premises as you would any other PHP application.&lt;/p&gt;
&lt;h3 id=&#34;emsa&#34;&gt;EMSA:&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Is an ingest system for HL7 or CSV-formatted lab reports from doctor offices, labs, and other official reporting groups.&lt;/li&gt;
&lt;li&gt;Allows exporting XML output to your disease surveillance system.&lt;/li&gt;
&lt;li&gt;Is a web console for managing messages.&lt;/li&gt;
&lt;li&gt;Allows you to see messages that are malformed.&lt;/li&gt;
&lt;li&gt;Has configurable rules for disease types and message routing; some messages may go directly into a surveillance system while others may be stored for later.&lt;/li&gt;
&lt;li&gt;Has configurable “automated workflows” to validate message information and perform tasks.&lt;/li&gt;
&lt;li&gt;Can be connected to a BI (Business Intelligence) tool for reporting, or the database can be queried directly.&lt;/li&gt;
&lt;li&gt;Works with integration packages like Rhapsody to bring in data from other systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;End Point has also made custom modifications to EMSA allowing address validation for incoming messages.&lt;/p&gt;
&lt;p&gt;EMSA can be deployed along with EpiTrax, the open source tracking system, or as a standalone application connected to a different disease surveillance system.&lt;/p&gt;
&lt;h3 id=&#34;benefits-of-emsa&#34;&gt;Benefits of EMSA&lt;/h3&gt;
&lt;p&gt;Aside from all the features mentioned above, EMSA as an open-source solution brings other benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reduced cost: Other than development and consulting to help set it up, there are no ongoing licensing costs.&lt;/li&gt;
&lt;li&gt;Designed to work as part of the open-source EpiTrax disease surveillance system.&lt;/li&gt;
&lt;li&gt;Participating in the open source community that is growing in the healthcare industry.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;help-is-available&#34;&gt;Help is available!&lt;/h3&gt;
&lt;p&gt;Interested in talking to us more about EMSA for your disease tracking? &lt;a href=&#34;/contact/&#34;&gt;Reach out today.&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;
&lt;p&gt;Emily Roberts, Rachelle Boulton, Josh Ridderhoff, Theron Jeppson. “Automated Processing of Electronic Data for Disease Surveillance.” &lt;em&gt;Online Journal of Public Health Informatics, 10(1):e2, May 22, 2018.&lt;/em&gt; &lt;a href=&#34;https://doi.org/10.5210/ojphi.v10i1.8903&#34;&gt;https://doi.org/10.5210/ojphi.v10i1.8903&lt;/a&gt;&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Fix for cdparanoia segmentation fault</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/08/cdparanoia-segmentation-fault/"/>
      <id>https://www.endpointdev.com/blog/2019/08/cdparanoia-segmentation-fault/</id>
      <published>2019-08-27T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2019/08/cdparanoia-segmentation-fault/5845424017_740bced716_o-edit.jpg&#34; alt=&#34;Compact disc close-up&#34; /&gt; &lt;a href=&#34;https://www.flickr.com/photos/jacd74/5845424017/&#34;&gt;Photo&lt;/a&gt; by Alberto Cabrera, cropped, &lt;a href=&#34;https://creativecommons.org/licenses/by/2.0/&#34;&gt;CC BY 2.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It had been a while since I last needed to rip a CD into audio files (and compress them), but the need recently arose. This particular disc was a language learning supplement to a book, and a CD was a reasonable way to distribute that.&lt;/p&gt;
&lt;p&gt;(Even though audio file downloads and streaming have become a far more common way to distribute audio than physical CDs, electronic formats don’t preserve our same rights to resell, lend, and make backups. But that is a topic for another blog post!)&lt;/p&gt;
&lt;p&gt;I was ripping the CD with &lt;a href=&#34;https://abcde.einval.com/wiki/&#34;&gt;abcde&lt;/a&gt; (A Better CD Encoder), a text-based CD ripping, tagging, and compressing front-end I have used often in the past. Unexpectedly I got an error, as shown in this terminal output:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-plain&#34; data-lang=&#34;plain&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Executing customizable pre-read function... done.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Getting CD track info... Querying the CD for audio tracks...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Grabbing entire CD - tracks:  01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;abcde: attempting to resume from /home/user/Music/abcde.2a0e162a..
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Grabbing track 28: Track 28...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdparanoia III release 10.2 (September 11, 2008)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Ripping from sector  131363 (track 28 [0:00.00])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          to sector  148524 (track 28 [3:48.61])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;outputting to /home/user/Music/abcde.2a0e162a/track28.wav
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt; (== PROGRESS == [ ! !!--!V V   ! ! !   &amp;gt;       | 143137 00 ] == :-) 0 ==)   /bin/abcde: line 3560: 21658 Segmentation fault      (core dumped) nice $READNICE $CDROMREADER -&amp;#34;$CDPARANOIACDROMBUS&amp;#34; &amp;#34;$CDROM&amp;#34; &amp;#34;${READTRACKNUMS:-$UTRACKNUM}&amp;#34; &amp;#34;$FILEARG&amp;#34; 1&amp;gt;&amp;amp;2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;[ERROR] abcde: The following commands failed to run:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;readtrack-28: cdparanoia  returned code 139
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Finished. Not cleaning /home/user/Music/abcde.2a0e162a.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The command that abcde was running when it died is:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cdparanoia -d /dev/cdrom &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;28&lt;/span&gt; /home/user/Music/abcde.2a0e162a/track28.wav&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Getting a segmentation fault suggests that the software has a bug, not necessarily that there is anything wrong with the disc, drive, or USB interface or drivers. But when troubleshooting, it is good (and easy) to start by testing on different hardware and operating system, if possible, to narrow things down.&lt;/p&gt;
&lt;p&gt;I was using Fedora 29 with an LG Blu-Ray USB drive. To rule out several of those possible factors, I tried ripping the disc again on another computer running Ubuntu 19.04 with a Samsung DVD USB drive. It crashed the same way on the same track.&lt;/p&gt;
&lt;p&gt;While looking online for ideas, I found several people mention bugfixes to cdparanoia in the wild from the open source community, which have not resulted in a new release.&lt;/p&gt;
&lt;p&gt;In fact, to my surprise, the popular cdparanoia can safely be classified as abandoned, since the last software release was almost 11 years ago, on 2008-09-11. More tellingly, the latest commit to the Subversion repository was on 2010-06-11.&lt;/p&gt;
&lt;p&gt;In the meantime, another project called &lt;a href=&#34;https://www.gnu.org/software/libcdio/&#34;&gt;libcdio&lt;/a&gt; contains a port of cdparanoia that was originally forked to make it work on platforms other than Linux. More importantly for my purpose here, it is actively maintained and has had many bugfixes that can be seen in the &lt;a href=&#34;https://github.com/rocky/libcdio-paranoia&#34;&gt;libcdio-paranoia Git repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Its executable is called cd-paranoia to distinguish it from the original cdparanoia, and abcde has an option (here specified in an environment variable) to use libcdio instead of the default cdparanoia. I invoked abcde this way:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#369&#34;&gt;CDROMREADERSYNTAX&lt;/span&gt;=libcdio abcde -d /dev/cdrom -o mp3,opus,flac -V -x -j &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and then the processing worked fine.&lt;/p&gt;
&lt;p&gt;An aside: You may notice my command above compresses into the &lt;code&gt;opus&lt;/code&gt; format. If you are not yet familiar with this &lt;a href=&#34;https://opus-codec.org/&#34;&gt;Opus audio codec&lt;/a&gt;, I recommend you take a look! Quoting their website, it is an:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;… open, royalty-free, highly versatile audio codec. Opus is unmatched for interactive speech and music transmission over the Internet, but is also intended for storage and streaming applications. It is standardized by the Internet Engineering Task Force (IETF) as RFC 6716 which incorporated technology from Skype’s SILK codec and Xiph.Org’s CELT codec.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;It is supported by many more players than I suspected given its youth, and I am very impressed with its quality vs. data size. If you are writing your own software that includes audio, Opus is a great choice, since you can include a playback library that supports it if needed.&lt;/p&gt;
&lt;p&gt;Anyway, back to cdparanoia: Sometimes it takes a bit of detective work to find the better-maintained free software, but often it is out there. Thanks to those who have kept working on cd-paranoia!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>LinuxFest Northwest 2019</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/05/linuxfest-northwest/"/>
      <id>https://www.endpointdev.com/blog/2019/05/linuxfest-northwest/</id>
      <published>2019-05-03T00:00:00+00:00</published>
      <author>
        <name>Josh Williams</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;a href=&#34;https://www.linuxfestnorthwest.org/conferences/2019&#34;&gt;&lt;img src=&#34;/blog/2019/05/linuxfest-northwest/LFNW2019.svg&#34; alt=&#34;LinuxFest Northwest Logo Creative Commons Attribution-ShareAlike 4.0 International License&#34; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I’m sitting in an airport, writing this in an attempt to stay awake. My flight is scheduled to depart at 11:59 PM, or 2:59 AM in the destination time zone which I’m still used to. This is the first red eye flight I’ve attempted, and I’m wondering why I’ve done this to myself.&lt;/p&gt;
&lt;p&gt;I have dedicated a good portion of my life to free, open source software. I’ll occasionally travel to conferences, sitting on long flights and spending those valuable weekends in talks about email encryption and chat bots. I’ve also done this to myself. But even with all this I have zero regrets.&lt;/p&gt;
&lt;p&gt;This little retrospective comes courtesy of my experience at &lt;a href=&#34;https://www.linuxfestnorthwest.org/conferences/2019&#34;&gt;LinuxFest Northwest&lt;/a&gt; this last weekend in Bellingham, Washington.&lt;/p&gt;
&lt;p&gt;Specifically I think it was some of the talks, painting things in broad strokes, that did it. I attended Jon “maddog” Hall’s beard-growing &lt;a href=&#34;https://lfnw.org/conferences/2019/program/proposals/247&#34;&gt;Fifty Years of Unix&lt;/a&gt;, and later sat in on the &lt;a href=&#34;https://lfnw.org/conferences/2019/program/proposals/344&#34;&gt;Q&amp;amp;A&lt;/a&gt;, which was a bit less technical than expected. So I didn’t ask about the “2038 problem.” But that’s okay.&lt;/p&gt;
&lt;p&gt;I felt a little guilty, on one hand, doing these general interest sessions instead of something on a much more specific topic, like ZFS, which would have arguably had a more direct benefit. On the other hand, doing those general interest talks helps me stay grounded, I suppose, helps me keep perspective.&lt;/p&gt;
&lt;p&gt;I did attend some more specialized talks, naturally. LFNW was a packed conference, often times there were a number of discussions I would have liked to attend happening at the same time. I’m hoping recordings will become available, or at least slides or other notes will appear. Some of the other talks I attended included, in no particular order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://lfnw.org/conferences/2019/program/proposals/278&#34;&gt;Audio Production on Linux&lt;/a&gt;&lt;br&gt;
Like many other End Pointers, I dabble in a little bit of music. Unlike those other End Pointers, I’ve got no talent for it. Still, I try, and so I listened in on this one to find out a little more about how Jack works. I also caught wind of PipeWire, a project that’s aiming to supplant both PulseAudio and Jack. Neat!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://lfnw.org/conferences/2019/program/proposals/179&#34;&gt;Using GIS in Postgres&lt;/a&gt;&lt;br&gt;
I’ve got a couple PostGIS-related projects coming up. So while this talk covered a few of the basics and some query types and didn’t touch much on indexing and such, it was still good to see. Plus it got me in place for mine.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://lfnw.org/conferences/2019/program/proposals/191&#34;&gt;Chat Ops&lt;/a&gt;&lt;br&gt;
Internally we have a few scripts that connect in to our chat system and write status messages. But it’s all one-way communication, like alerts from our monitoring system. This talk proposed writing bots that can watch for and process messages written by others, and take actions. We could, for example, acknowledge alerts or set systems into downtime without having to switch out of the chat system. I want to look into options like that for sure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href=&#34;https://lfnw.org/conferences/2019/program/proposals/337&#34;&gt;We can fix email server encryption!&lt;/a&gt;&lt;br&gt;
End Point (thankfully) is on the verge of turning down its internal email systems. But the current trouble with server-to-server email encryption was still valuable to learn about!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yeah, I did one, too: &lt;a href=&#34;http://lfnw.org/conferences/2019/program/proposals/327&#34;&gt;Get It Back, A New PostgreSQL Admin’s Guide to Redundancy and Recovery&lt;/a&gt;. I talked about the different methods of performing backups of a Postgres database, and the advantages and drawbacks of each. Plus a little about replication. The slides are up &lt;a href=&#34;https://joshwilliams.name/get-it-back/&#34;&gt;here&lt;/a&gt;, but admittedly only about half the material is there, the rest would have been me talking at the slides. I’ll write it up in a subsequent post, promise. Afterward I stuck around to sit in on the &lt;a href=&#34;https://lfnw.org/conferences/2019/program/proposals/331&#34;&gt;Consult the Experts&lt;/a&gt; panel.&lt;/p&gt;
&lt;p&gt;Plus my talk was attended by two other End Pointers, Greg Davidson and Greg Hanson. No pressure there.&lt;/p&gt;
&lt;p&gt;But seriously, that was an added bonus. Being a distributed company much of the time our coworkers are right at our fingertips in one sense, but in another also out of reach. Getting that face time was very nice. LFNW had a couple general social activities, too, on Friday and Saturday. And of course I attended both, talking about Nagios and Haskell and Docker and anything else that came to mind. Florida to Washington was a long hike, but it was well worth it.&lt;/p&gt;
&lt;p&gt;All that said, I do have one lingering regret: By the time I figured out &lt;em&gt;that&lt;/em&gt; there were tie-dye shirts, a trademark of this conference, and &lt;em&gt;where&lt;/em&gt; they were, they were all gone. Oh well. Next year.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Switching from Google Maps to Leaflet</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2019/03/switching-google-maps-leaflet/"/>
      <id>https://www.endpointdev.com/blog/2019/03/switching-google-maps-leaflet/</id>
      <published>2019-03-23T00:00:00+00:00</published>
      <author>
        <name>Juan Pablo Ventoso</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2019/03/switching-google-maps-leaflet/leaflet-weather-map-us.jpg&#34; alt=&#34;Leaflet Weather map example&#34; /&gt;&lt;br&gt;Photo: &lt;a href=&#34;https://www.extendedforecast.net/radsat&#34;&gt;RadSat HD&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It’s no news for anyone who has Google Maps running on their websites that Google started charging for using their API. We saw it coming when, back in 2016, they started requiring a key to add a map using their JavaScript API. And on June 11, 2018, they did a major upgrade to their API and billing system.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;The consequence?&lt;/b&gt; Any website with more than 25,000 page loads per day will have to pay. And if you are using a dynamic map (a map with custom styling and/or content) you only have roughly 28,000 free monthly page loads. We must create a billing account, &lt;em&gt;even if we have a small website with a couple of daily visitors&lt;/em&gt;, hand credit card information to Google, and monitor our stats to make sure we won’t be charged. And if we don’t do that, our map will be dark and will have a “For development only” message in the background.&lt;/p&gt;
&lt;p&gt;So what are your options? You can either pay or completely remove Google Maps from your websites. Even enterprise weather websites like &lt;a href=&#34;https://weather.com/weather/radar/interactive/l/USNY0996:1:US&#34;&gt;The Weather Channel&lt;/a&gt; or &lt;a href=&#34;https://www.wunderground.com/wundermap&#34;&gt;Weather Underground&lt;/a&gt; have now replaced their Google Maps API calls with an alternative like Leaflet or MapBox (in some cases, they even gained some functionality in the process).&lt;/p&gt;
&lt;p&gt;I have a &lt;a href=&#34;https://www.extendedforecast.net&#34;&gt;personal weather website&lt;/a&gt;, and when I heard big changes were coming, I started to move away from Google Maps as well. My choice at that moment was Leaflet: It has everything you may need to build a robust tile-based map, add layers, markers, animations, custom tiles, etc. And it’s BSD-licensed &lt;b&gt;open source and free&lt;/b&gt;.&lt;/p&gt;
&lt;h3 id=&#34;creating-a-basic-map&#34;&gt;Creating a basic map&lt;/h3&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/03/switching-google-maps-leaflet/google-vs-leaflet-look-and-feel.jpg&#34; /&gt;&lt;br&gt;&lt;small&gt;Google Map conversion to Leaflet can be almost seamless if the same tiles are used.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;Google Maps API and Leaflet share a similar way of doing most things, but they have some key differences we need to take into account. As a general rule, Google used the “google.maps” prefix to name most classes and interfaces, while Leaflet uses the “L” prefix instead.&lt;/p&gt;
&lt;p&gt;First thing we need to do is to remove the Google Maps API reference from our website(s). So we need to replace the reference:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;script type=&amp;#34;text/javascript&amp;#34; src=&amp;#34;https://maps.googleapis.com/maps/api/js?key=[your_api_key]&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;With the references to the Leaflet map JavaScript and stylesheet URIs.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;script src=&amp;#34;https://unpkg.com/leaflet@1.0.2/dist/leaflet.js&amp;#34;&amp;gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;link rel=&amp;#34;stylesheet&amp;#34; href=&amp;#34;https://unpkg.com/leaflet@1.0.2/dist/leaflet.css&amp;#34; /&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now let’s take a look at the code needed to create a Google Map vs. a Leaflet map.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; map = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; google.maps.Map(&lt;span style=&#34;color:#038&#34;&gt;document&lt;/span&gt;.getElementById(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;map&amp;#34;&lt;/span&gt;), {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	center: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; google.maps.LatLng(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;40.7401&lt;/span&gt;, -&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;73.9891&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	zoom: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;12&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	mapTypeId: google.maps.MapTypeId.ROADMAP
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Leaflet:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; map = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; L.Map(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;map&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	center: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; L.LatLng(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;40.7401&lt;/span&gt;, -&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;73.9891&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	zoom: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;12&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	layers: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; L.TileLayer(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;https://tile.openstreetmap.org/{z}/{x}/{y}.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Quite similar, isn’t it? The main difference is that, in Leaflet, we need to provide a tile layer for the base map because there isn’t one by default. There are a lot of excellent tile layers available to use at no cost. Here are some of them:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Bright&lt;/b&gt;: &lt;code&gt;https://a.tiles.mapbox.com/v3/mapbox.world-bright/{z}/{x}/{y}.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Topographic&lt;/b&gt;: &lt;code&gt;https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Black and white&lt;/b&gt;: &lt;code&gt;https://stamen-tiles-{s}.a.ssl.fastly.net/toner/{z}/{x}/{y}{r}.png&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can browse other free tile layer providers for Leaflet on &lt;a href=&#34;https://leaflet-extras.github.io/leaflet-providers/preview/&#34;&gt;this link&lt;/a&gt;. And of course, if you want to pay there’s a lot of affordable paid tiles out there too.&lt;/p&gt;
&lt;h3 id=&#34;adding-a-marker&#34;&gt;Adding a marker&lt;/h3&gt;
&lt;p&gt;Adding a marker is quite straightforward as well. It even looks easier on Leaflet than Google.&lt;/p&gt;
&lt;p&gt;Google:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; marker = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; google.maps.Marker({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	position: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; google.maps.LatLng(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;40.7401&lt;/span&gt;, -&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;73.9891&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	map: map,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	title: &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;End Point Corporation&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;});&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Leaflet:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; marker = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; L.Marker(&lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; L.LatLng(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;40.7401&lt;/span&gt;, -&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;73.9891&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;marker.bindPopup(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;End Point Corporation&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;map.addLayer(marker);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And that’s it: we have a working Leaflet map with a marker that displays a text when we click on it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/03/switching-google-maps-leaflet/leaflet-example-working.jpg&#34; /&gt;&lt;br&gt;&lt;small&gt;Screenshot of the Leaflet example. Code below, if you want to try it live:&lt;/small&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;Leaflet map example — End Point Corporation&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;title&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;src&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;https://unpkg.com/leaflet@1.0.2/dist/leaflet.js&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;link&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;rel&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;stylesheet&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;href&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;https://unpkg.com/leaflet@1.0.2/dist/leaflet.css&amp;#34;&lt;/span&gt; /&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;head&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt; { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;margin&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;0&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    #&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;map&lt;/span&gt; { &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;height&lt;/span&gt;: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;100&lt;/span&gt;&lt;span style=&#34;color:#888;font-weight:bold&#34;&gt;%&lt;/span&gt; };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;style&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;id&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;map&amp;#34;&lt;/span&gt;&amp;gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;div&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt; &lt;span style=&#34;color:#369&#34;&gt;type&lt;/span&gt;=&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;text/javascript&amp;#34;&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; endPointLocation = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; L.LatLng(&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;40.7401&lt;/span&gt;, -&lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;73.9891&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; map = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; L.Map(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;map&amp;#34;&lt;/span&gt;, {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      center: endPointLocation,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      zoom: &lt;span style=&#34;color:#00d;font-weight:bold&#34;&gt;12&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      layers: &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; L.TileLayer(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;https://tile.openstreetmap.org/{z}/{x}/{y}.png&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;var&lt;/span&gt; marker = &lt;span style=&#34;color:#080;font-weight:bold&#34;&gt;new&lt;/span&gt; L.Marker(endPointLocation);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    marker.bindPopup(&lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;End Point Corporation&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    map.addLayer(marker);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;script&lt;/span&gt;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/&lt;span style=&#34;color:#b06;font-weight:bold&#34;&gt;body&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id=&#34;layers-and-controls&#34;&gt;Layers and controls&lt;/h3&gt;
&lt;p&gt;From this point, we can start doing more complex things if we need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Display images on the map&lt;/b&gt;: &lt;a href=&#34;https://leafletjs.com/reference-1.4.0.html#imageoverlay&#34;&gt;ImageOverlay&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Display a custom tile layer&lt;/b&gt;: &lt;a href=&#34;https://leafletjs.com/reference-1.4.0.html#tilelayer&#34;&gt;TileLayer&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Draw polygons, rectangles, circles&lt;/b&gt;: &lt;a href=&#34;https://leafletjs.com/reference-1.4.0.html#polygon&#34;&gt;Polygon&lt;/a&gt; - &lt;a href=&#34;https://leafletjs.com/reference-1.4.0.html#rectangle&#34;&gt;Rectangle&lt;/a&gt; - &lt;a href=&#34;https://leafletjs.com/reference-1.4.0.html#circle&#34;&gt;Circle&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Display GeoJSON data on the map&lt;/b&gt;: &lt;a href=&#34;https://leafletjs.com/reference-1.4.0.html#geojson&#34;&gt;GeoJSON&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can browse the &lt;a href=&#34;https://leafletjs.com/reference-1.4.0.html&#34;&gt;Leaflet API reference&lt;/a&gt; for further details.&lt;/p&gt;
&lt;h3 id=&#34;plugins-and-tools&#34;&gt;Plugins and tools&lt;/h3&gt;
&lt;p&gt;There is some extended functionality in Google Maps that is not available in Leaflet by default unless we use a plugin. For example, if we want to add the “fullscreen” button to the top right corner, just as Google has it, or if we want to let the user draw polygons on top of the map, we’ll need to download and add the reference to the required plugins. Here is a list of the ones I’ve already used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;“Fullscreen” button plugin&lt;/b&gt;: &lt;a href=&#34;https://github.com/Leaflet/Leaflet.fullscreen&#34;&gt;Leaflet.fullscreen&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Vector drawing and editing plugin&lt;/b&gt;: &lt;a href=&#34;https://github.com/Leaflet/Leaflet.draw&#34;&gt;Leaflet.draw&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Heatmap plugin&lt;/b&gt;: &lt;a href=&#34;https://github.com/Leaflet/Leaflet.heat&#34;&gt;Leaflet.heat&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find more plugins at the &lt;a href=&#34;https://github.com/Leaflet/&#34;&gt;Leaflet GitHub account&lt;/a&gt;. And of course, you can (and should!) contribute to improve them.&lt;/p&gt;
&lt;p&gt;There is also some alternatives to additional services offered by Google like geocoding or routing. They might have some limitations involved, so it would be wise to take a look at their usage policies first.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;b&gt;Geocoding API&lt;/b&gt;: &lt;a href=&#34;https://wiki.openstreetmap.org/wiki/Nominatim&#34;&gt;Nominatim&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Routing&lt;/b&gt;: &lt;a href=&#34;http://project-osrm.org/&#34;&gt;Project ORSM&lt;/a&gt; (free version has limited use).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More services can be found at &lt;a href=&#34;https://switch2osm.org/other-uses/&#34;&gt;switch2osm.org/other-uses&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&#34;putting-it-all-together&#34;&gt;Putting it all together&lt;/h3&gt;
&lt;p&gt;I’ve been using Leaflet for almost a year now in an interactive weather map originally made with the Google Maps API. Of course, I’ve had some minor hiccups along the way, but having full control of the source code and resources allows you to add functionality, fix things or even rewrite whatever you need.&lt;/p&gt;
&lt;p&gt;The Leaflet source code is well organized, modularized and easy to understand. I’ve created custom grid layers using different tile sources, with different coordinate systems, animations with frame transitions, custom controls, clickable polygons, popups with dynamic content from AJAX calls, and more. And all works smoothly. So I recommend that you &lt;b&gt;go ahead and start using Leaflet right away&lt;/b&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;/blog/2019/03/switching-google-maps-leaflet/leaflet-map-radsat-hd.jpg&#34; /&gt;&lt;br&gt;&lt;small&gt;Example of a fully-functional Leaflet map with custom controls, overlays, animations and polygons.&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;And this is the repository with my weather map source code: &lt;a href=&#34;https://github.com/juanpabloventoso/RadSat-HD&#34;&gt;RadSat HD&lt;/a&gt;. Feel free to leave any comments or suggestions!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Start basic application with Vue.js 2 and Drupal 8</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2018/04/start-basic-application-with-vue-drupal/"/>
      <id>https://www.endpointdev.com/blog/2018/04/start-basic-application-with-vue-drupal/</id>
      <published>2018-04-05T00:00:00+00:00</published>
      <author>
        <name>Piotr Hankiewicz</name>
      </author>
      <content type="html">
        &lt;img src=&#34;/blog/2018/04/start-basic-application-with-vue-drupal/vue-and-drupal.jpg&#34; width=&#34;1200&#34; alt=&#34;Vue.js 2 and Drupal 8&#34; /&gt;
&lt;h3 id=&#34;introduction&#34;&gt;Introduction&lt;/h3&gt;
&lt;p&gt;The purpose of creating this post is to show how fast can you build web applications with Vue.js on the front-end and Drupal on the back-end side.&lt;/p&gt;
&lt;p&gt;Let’s call our project “Awesome Nerds”.&lt;/p&gt;
&lt;h3 id=&#34;what-do-we-need&#34;&gt;What do we need?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Debian/Ubuntu system&lt;/li&gt;
&lt;li&gt;Internet&lt;/li&gt;
&lt;li&gt;30 minutes&lt;/li&gt;
&lt;li&gt;Vagrant &amp;amp; VirtualBox&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Vim&lt;/li&gt;
&lt;li&gt;Yarn&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;step-by-step&#34;&gt;Step by step&lt;/h3&gt;
&lt;p&gt;Here’s what we’re going to do:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Install Vagrant, VirtualBox, and Git&lt;/li&gt;
&lt;li&gt;Setup a new Drupal 8 project that will be our back-end project&lt;/li&gt;
&lt;li&gt;Setup a new Vue.js project that will be our front-end application&lt;/li&gt;
&lt;li&gt;Let’s code&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;install-vagrant-virtualbox-and-git&#34;&gt;Install Vagrant, VirtualBox, and Git&lt;/h3&gt;
&lt;p&gt;Open your console and run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ sudo apt-get install software-properties-common&lt;/code&gt; - getting some common libraries&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ sudo apt-add-repository ppa:ansible/ansible&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ sudo apt-get update&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ sudo apt-get install ansible&lt;/code&gt; - installing Ansible&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ wget ‘https://releases.hashicorp.com/vagrant/2.0.2/vagrant_2.0.2_x86_64.deb’ &amp;amp;&amp;amp; dpkg -i vagrant_2.0.2_x86_64.deb&lt;/code&gt; - installing Vagrant&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ sudo apt-get install dkms&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ deb https://download.virtualbox.org/virtualbox/debian &amp;lt;mydist&amp;gt; contrib&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ wget -q https://www.virtualbox.org/download/oracle_vbox.asc -O- | sudo apt-key add -&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ sudo apt-get update&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ sudo apt-get install virtualbox-5.2 git vim nfs-kernel-server&lt;/code&gt; - installing VirtualBox&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ vagrant plugin install vagrant-vbguest&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ echo &amp;quot;deb https://dl.yarnpkg.com/debian/ stable main&amp;quot; | sudo tee /etc/apt/sources.list.d/yarn.list&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install yarn&lt;/code&gt; - installing Yarn&lt;/p&gt;
&lt;p&gt;Now we can continue to the back-end project installation.&lt;/p&gt;
&lt;h3 id=&#34;install-and-setup-back-end-project&#34;&gt;Install and setup back-end project&lt;/h3&gt;
&lt;p&gt;Create a new folder for the project:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ mkdir awesome_nerds &amp;amp;&amp;amp; cd awesome_nerds&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ mkdir frontend backend&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;We need to clone the DrupalVM repository. DrupalVM is a Drupal setup that helps to encapsulate services with Vagrant. Run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ cd backend&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ git clone git@github.com:geerlingguy/drupal-vm.git .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Let’s name our project:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ vim default.config.yml&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Look and set these two settings so they look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vagrant_hostname: awesomenerds.backend                                   
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vagrant_machine_name: awesomenerds_backend&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Quit Vim and run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ vagrant up&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It will take a while to set up everything, you can get a coffee or browse some memes or go to the next chapter and start creating our front-end project.&lt;/p&gt;
&lt;p&gt;When it’s ready we will have a running Drupal 8 setup with MySQL, PHP 7, and Apache (you can configure this stack in &lt;code&gt;default.config.yml&lt;/code&gt; if you prefer nginx for example).&lt;/p&gt;
&lt;p&gt;Drupal project files are in the &lt;code&gt;drupal&lt;/code&gt; directory and that’s the only folder that you would want to add to a project Git repository.&lt;/p&gt;
&lt;h3 id=&#34;setup-new-vuejs-project&#34;&gt;Setup new Vue.js project&lt;/h3&gt;
&lt;p&gt;We will use a minimal project skeleton from &lt;a href=&#34;https://github.com/vuejs-templates/webpack&#34;&gt;https://github.com/vuejs-templates/webpack&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ cd awesome_nerds/frontend&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ git clone https://github.com/vuejs-templates/webpack .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ yarn install -g vue-cli&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ vue init webpack awesome_nerds&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Name the project “awesome_nerds” (yes!) and just hit enter to install with defaults.&lt;/p&gt;
&lt;p&gt;When you run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;$ yarn run dev&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;you will get a fresh Vue.js application running on http://localhost:8080.&lt;/p&gt;
&lt;h3 id=&#34;lets-code&#34;&gt;Let’s code!&lt;/h3&gt;
&lt;p&gt;Now we are ready for development. It can be really rapid, both Vue.js 2 and Drupal 8 are impressively good and it’s just a matter of finding a good idea for your new start-up.&lt;/p&gt;
&lt;p&gt;In my next post I will continue and code a simple social application using the REST API of Drupal and our Vue.js front-end.&lt;/p&gt;
&lt;p&gt;Thank you and good luck!&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Vue in Ecommerce: Routing and Persistence</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2018/03/vue-router-in-ecommerce/"/>
      <id>https://www.endpointdev.com/blog/2018/03/vue-router-in-ecommerce/</id>
      <published>2018-03-01T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2018/03/vue-router-in-ecommerce/vue-shop.png&#34; alt=&#34;Vue Shop created by Matheus Azzi&#34; /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;I recently wrote about &lt;a href=&#34;/blog/2018/02/vue-in-ecommerce/&#34;&gt;Vue in Ecommerce&lt;/a&gt; and pointed to a handful of references to get started. Today, I’ll talk about using &lt;a href=&#34;https://router.vuejs.org/en/&#34;&gt;vue-router&lt;/a&gt; in a small ecommerce application, combined with &lt;a href=&#34;https://www.npmjs.com/package/vuex-persist&#34;&gt;vuex-persist&lt;/a&gt; for state storage.&lt;/p&gt;
&lt;p&gt;I forked this &lt;a href=&#34;https://github.com/matheusazzi/shop-vue&#34;&gt;Vue Shop on GitHub&lt;/a&gt; from Matheus Azzi. It was a great starting point for to see how basic component organization and state management might look in a Vue ecommerce application, but it is a single page ecommerce app with no separate page for a product detail, checkout, or static pages, so here I go into some details on routing and persistence in a Vue ecommerce application.&lt;/p&gt;
&lt;h3 id=&#34;vue-router&#34;&gt;Vue Router&lt;/h3&gt;
&lt;p&gt;In looking through the documentation, I don’t see a great elevator pitch on what it is that Vue Router does. If you are new to routing, it’s a tool to map the URL request to the Vue component. Since I’m coming from the Rails perspective, I’m quite familiar with the Ruby on Rails routing from URL pattern matching, constraints, resources to Ruby on Rails controllers and actions. Vue routing via vue-router has some similar elements.&lt;/p&gt;
&lt;p&gt;When you create a basic Vue application via vue-cli, you are given the option to include vue-router:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;vue init webpack myapp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;? Project name myapp
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;? Project description A Vue.js project
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;? Author Steph &amp;lt;steph@endpoint.com&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;? Vue build standalone
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;? Install vue-router? (Y/n) &lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you select Yes here, the main differences you’ll see are that an application with vue-router installed will call &lt;code&gt;&amp;lt;router-view/&amp;gt;&lt;/code&gt; to render the view for the current router, instead of a &lt;code&gt;&amp;lt;HelloWorld/&amp;gt;&lt;/code&gt; component, and that a vue-router app will include src/router/index.js with basic routing configuration to your &lt;code&gt;&amp;lt;HelloWorld/&amp;gt;&lt;/code&gt; component.&lt;/p&gt;
&lt;h4 id=&#34;without-routing&#34;&gt;Without Routing&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// App.vue without vue-router
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;template&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;div id=&amp;#34;app&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;img src=&amp;#34;./assets/logo.png&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;HelloWorld/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/template&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import HelloWorld from &amp;#39;./components/HelloWorld&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export default {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name: &amp;#39;App&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  components: {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    HelloWorld
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h4 id=&#34;with-routing&#34;&gt;With Routing&lt;/h4&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// App.vue with vue-router
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;template&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;div id=&amp;#34;app&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;img src=&amp;#34;./assets/logo.png&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &amp;lt;router-view/&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &amp;lt;/div&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/template&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export default {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  name: &amp;#39;App&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&amp;lt;/script&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// src/router/index.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import Vue from &amp;#39;vue&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import Router from &amp;#39;vue-router&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import HelloWorld from &amp;#39;@/components/HelloWorld&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Vue.use(Router)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export default new Router({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  routes: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      path: &amp;#39;/&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      name: &amp;#39;HelloWorld&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      component: HelloWorld
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Moving on, building out an ecommerce app with some basic page set up, not including user accounts, might look like the setup below, which includes basic components and routes for the Home, Cart, Checkout, Receipt, and About page. This simplified set-up doesn’t even include a product detail page, but one could accomplish that with a bit of URL matching via parameters.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// src/router/index.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import Vue from &amp;#39;vue&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import Router from &amp;#39;vue-router&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import Home from &amp;#39;@/components/pages/Home&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import Receipt from &amp;#39;@/components/pages/Receipt&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import About from &amp;#39;@/components/pages/About&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import Cart from &amp;#39;@/components/pages/Cart&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import Checkout from &amp;#39;@/components/pages/Checkout&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Vue.use(Router)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export default new Router({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  mode: &amp;#39;history&amp;#39;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  routes: [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { path: &amp;#39;/&amp;#39;, component: Home },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { path: &amp;#39;/receipt&amp;#39;, component: Receipt },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { path: &amp;#39;/about&amp;#39;, component: About },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { path: &amp;#39;/cart&amp;#39;, component: Cart },
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    { path: &amp;#39;/checkout&amp;#39;, component: Checkout }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h5 id=&#34;history-mode&#34;&gt;History Mode&lt;/h5&gt;
&lt;p&gt;One thing you also might notice is the ‘history’ mode setting above, overriding the default hash mode. You can read about that &lt;a href=&#34;https://router.vuejs.org/en/essentials/history-mode.html&#34;&gt;here&lt;/a&gt;, but it leverages &lt;code&gt;history.pushState&lt;/code&gt; to URL navigation. The history mode override must be combined with server configuration to ensure that non-static asset URL requests (i.e. all of our routes) hit the Vue index.html app in our Vue application, and then renders the component associated with the requested route. I’m using Apache for my Vue application, so I followed the instructions on that documentation for vue-router history mode configuration.&lt;/p&gt;
&lt;h5 id=&#34;using-router-link&#34;&gt;Using &amp;lt;router-link/&amp;gt;&lt;/h5&gt;
&lt;p&gt;One thing you also will want to use throughout your templates is &lt;code&gt;&amp;lt;router-link/&amp;gt;&lt;/code&gt;, instead of hard-coding rigid URL paths. For example, instead of linking to the cart via &lt;code&gt;&amp;lt;a href=&amp;quot;/cart&amp;quot;&amp;gt;Cart&amp;lt;/a&amp;gt;&lt;/code&gt;, you’ll want to link via &lt;code&gt;&amp;lt;router-link to=&amp;quot;cart&amp;quot;&amp;gt;Cart&amp;lt;/router-link&amp;gt;&lt;/code&gt;. This will be resolve to the proper link upon rendering depending on your routing configuration.&lt;/p&gt;
&lt;h3 id=&#34;vuex-persistedstate&#34;&gt;Vuex PersistedState&lt;/h3&gt;
&lt;p&gt;If you are still reading, you’ve now seen the basic configuration for vue-router in an ecommerce application running on Vue. The ecommerce application I forked uses &lt;a href=&#34;https://vuex.vuejs.org/en/intro.html&#34;&gt;vuex&lt;/a&gt;, a state management pattern and library to store the state of our application. What you’ll notice with a shopping cart application, though, is that you need to persist the state of your cart for sessions. Without further setup, every page refresh on your application will result in losing the state of the cart.&lt;/p&gt;
&lt;p&gt;To address this behavior, there are a few options to persist your state in Vue. I chose &lt;a href=&#34;https://www.npmjs.com/package/vuex-persist&#34;&gt;vuex-persist&lt;/a&gt;, which stores the state (for all modules or specific modules) in localStorage. After installing vuex-persist, I modified the following to include the shoppingCart module in my stored state:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// src/store/index.js
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import Vue from &amp;#39;vue&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import Vuex from &amp;#39;vuex&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;import VuexPersistence from &amp;#39;vuex-persist&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;// import other stuff
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Vue.use(Vuex)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;const vuexLocal = new VuexPersistence({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  storage: window.localStorage,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  reducer: state =&amp;gt; ({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    shoppingCart: state.shoppingCart
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export default new Vuex.Store({
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  // ...
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  plugins: [vuexLocal.plugin]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;It’s as simple as that to persist the state of your shopping cart using all the vuex-persist defaults, and there are some alternative Promise-based stores supported if you are interested.&lt;/p&gt;
&lt;h3 id=&#34;whats-next&#34;&gt;What’s Next?&lt;/h3&gt;
&lt;p&gt;What’s next in building out your ecommerce application? From here, one might consider adding user authentication &amp;amp; authorization support, as well as support for retrieving and rendering products in the product list view. I hope to address those in upcoming blog posts in this series on Vue in ecommerce.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Vue and Ecommerce: An Introduction</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2018/02/vue-in-ecommerce/"/>
      <id>https://www.endpointdev.com/blog/2018/02/vue-in-ecommerce/</id>
      <published>2018-02-19T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2018/02/vue-in-ecommerce/vue-shop.png&#34; alt=&#34;Vue Shop created by Matheus Azzi&#34; /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;I speak the domain specific language of ecommerce, Ruby on Rails, JavaScript, and jQuery. Lately, I’ve been getting up to speed on &lt;a href=&#34;https://vuejs.org/&#34;&gt;Vue.js&lt;/a&gt;. I’ve been working on writing a small ecommerce site using Vue.js, because for me, creating an application addressing familiar paradigm in a new technology is a great way to learn.&lt;/p&gt;
&lt;p&gt;Vue.js, initially released about 3 years ago, is a lightweight JS framework that can be adopted incrementally. In the case of my small example site, Vue.js serves the frontend shop content and connects to a decoupled backend that can be run on any platform of choice. On my path to get up to speed on Vue, I found great resources that I wanted to write about before I get into the details of my ecommerce app. Here they are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, I started with the &lt;a href=&#34;https://vuejs.org/v2/guide/&#34;&gt;Vue.js documentation&lt;/a&gt;. The documentation is great as a starting point to understand some of the terminology. I often don’t love the documentation that comes with technologies, but I found the &lt;b&gt;Essentials&lt;/b&gt; section in the Vue documentation to be a great launching point.&lt;/li&gt;
&lt;li&gt;After I worked through some of the Vue.js documentation, I did a simple search for “&lt;a href=&#34;https://www.google.com/search?q=vue+jsfiddle&#34;&gt;vue jsfiddle&lt;/a&gt;” and experimented with a few of those fiddles. Having come from a jQuery background (with &lt;a href=&#34;https://mustache.github.io/&#34;&gt;mustache&lt;/a&gt;), I was familiar with how templating and logic inside a template might work, but less familiar with &lt;a href=&#34;https://vuejs.org/v2/guide/instance.html&#34;&gt;the vue instance&lt;/a&gt; and &lt;a href=&#34;https://vuejs.org/v2/guide/class-and-style.html&#34;&gt;binding&lt;/a&gt;, so I targeted jsfiddles with this in mind.&lt;/li&gt;
&lt;li&gt;Next, I looked for resources with Vue + ecommerce specifically. Here’s where I found some good stuff:
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://scotch.io/courses/build-an-online-shop-with-vue/introduction&#34;&gt;Scotch.io Course on Vue + Ecommerce&lt;/a&gt;: This online course has a great amount of overlap with the main documentation of Vue, but some of the examples are ecommerce specific, which serves as a good introduction.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://medium.com/@connorleech/standing-on-the-shoulders-of-giants-node-js-vue-2-stripe-heroku-and-amazon-s3-c6fe03ee1118&#34;&gt;Vue + Third Party Integration for Ecommerce&lt;/a&gt;: This Medium article talks about integrating third party tools used with Vue (Stripe, AWS S3, Heroku) to build an ecommerce site. While this post doesn’t go into the nitty gritty details of a Vue application, it does provide many code examples for checkout and payment integration, although noting that &lt;a href=&#34;https://github.com/fromAtoB/vue-stripe-elements&#34;&gt;vue-stripe-elements&lt;/a&gt; is now the module of choice for Stripe integration in Vue.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://vuejsfeed.com/blog/vue-js-storefront-pwa-for-ecommerce&#34;&gt;Vue API App + Magento Backend&lt;/a&gt;: This is a front-end open source Vue application designed to pair with a Magento backend, capitalizing on the popularity of Magento.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://moltin.com/&#34;&gt;Moltin&lt;/a&gt;: This company is providing backend microservices for ecommerce, with Vue.js support (and other technologies) to build out your store.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://travishorn.com/vue-online-store-with-shopping-cart-c072433f8d9e&#34;&gt;Vue + Ecommerce with Vuex&lt;/a&gt;: Here’s another Medium article which features &lt;a href=&#34;https://vuex.vuejs.org/en/&#34;&gt;vuex&lt;/a&gt;, the state management library in Vue, essentially going through examples of state management to manage a shopping cart.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://codepen.io/mjweaver01/pen/yerzox&#34;&gt;CodePen Vue + Ecommerce Example&lt;/a&gt;: Here’s a standalone CodePen example of a Vue shopping cart. While I find this a little hard to parse because there’s a significant amount of code in the example, you can download and experiment with it.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/sdras/sample-vue-shop&#34;&gt;Stripe Integration Example&lt;/a&gt;: Here’s a GitHub repo that demonstrates Stripe integration in serverless Vue shop.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/matheusazzi/shop-vue&#34;&gt;Basic Vue Shop on GitHub&lt;/a&gt;: Why reinvent the wheel when someone has already built out product listing and cart functionality? After reviewing all the examples above, this was my choice for a starting point in building out a Vue shop, but it is a basic starter point, without the use of &lt;a href=&#34;https://router.vuejs.org/en/&#34;&gt;vue-router&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;vue-libraries--dependencies&#34;&gt;Vue Libraries / Dependencies&lt;/h3&gt;
&lt;p&gt;You might have noticed I mentioned a few Vue libraries and tools. Here are additional resources for getting up to speed on those dependencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installing Node.js and npm, if you aren’t already familiar with it. &lt;a href=&#34;https://www.quora.com/How-does-npm-compare-to-other-packaging-systems-like-Ruby-gems-and-Pythons-pip&#34;&gt;Here&lt;/a&gt; is a great comparison if you are coming from the Rails space.&lt;/li&gt;
&lt;li&gt;Already mentioned, &lt;a href=&#34;https://vuex.vuejs.org/en/intro.html&#34;&gt;vuex&lt;/a&gt; state management in Vue.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://router.vuejs.org/en/&#34;&gt;vue-router&lt;/a&gt;. In an ecommerce site, you would possibly have routing for the index, product detail, cart, checkout, user account, order detail pages.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/vuejs/vue-cli&#34;&gt;vue-cli&lt;/a&gt;: simple command line interface for scaffolding Vue projects. Think “rails generate &amp;hellip;” for Vue.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/pagekit/vue-resource&#34;&gt;vue-resource&lt;/a&gt;, or a comparable module (&lt;a href=&#34;https://alligator.io/vuejs/rest-api-axios/&#34;&gt;vue-axios&lt;/a&gt;) for making API and AJAX requests.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;incremental-adoption&#34;&gt;Incremental Adoption&lt;/h3&gt;
&lt;p&gt;While I am writing a Vue ecommerce shop based on the shop-vue GitHub repo mentioned above, in the ecommerce space, we commonly take on incremental ecommerce updates rather than “The Big Rewrite”, as noted in &lt;a href=&#34;/blog/2017/12/enhancing-your-sites-with-vue/&#34;&gt;Greg’s article&lt;/a&gt;. Components that could be introduced incrementally into existing ecommerce applications might include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dynamic marketing elements on homepage or product listing page, e.g. promo tools, “Customers Also Looked at”, “Products Recently Viewed”&lt;/li&gt;
&lt;li&gt;Cart + Checkout process (standalone component)&lt;/li&gt;
&lt;li&gt;Admin elements for data management and processing&lt;/li&gt;
&lt;li&gt;User account section&lt;/li&gt;
&lt;/ul&gt;

      </content>
    </entry>
  
    <entry>
      <title>Sunsetting Piggybak, A Ruby on Rails Ecommerce Gem</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2018/02/sunsetting-piggybak/"/>
      <id>https://www.endpointdev.com/blog/2018/02/sunsetting-piggybak/</id>
      <published>2018-02-14T00:00:00+00:00</published>
      <author>
        <name>Steph Skardal</name>
      </author>
      <content type="html">
        &lt;p&gt;&lt;img src=&#34;/blog/2018/02/sunsetting-piggybak/piggybak.jpg&#34; alt=&#34;Screenshot of website for Piggybak: Open Source Ruby on Rails Ecommerce&#34; /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;Hi there! I’m Steph, the original creator and not-very-good maintainer
of &lt;a href=&#34;https://github.com/piggybak/piggybak&#34;&gt;Piggybak&lt;/a&gt;, a modular ecommerce
platform written in Ruby on Rails. With the help of End Point, I created
Piggybak after building a custom Ruby on Rails ecommerce application
for one of our clients here at End Point.&lt;/p&gt;
&lt;p&gt;My goal with Piggybak was to create a lightweight, modular ecommerce
platform in the form of a Ruby on Rails gem that could be combined with
other Ruby on Rails gems for quick setup. Over the years, End Point has
had much success working in &lt;a href=&#34;http://www.icdevgroup.org/&#34;&gt;Interchange&lt;/a&gt;, an
ecommerce framework written in Perl.  The web stack has evolved greatly
over the years, as has the capacity for modularity and the ability to
decouple front-end and back-end architecture.&lt;/p&gt;
&lt;p&gt;Fast forward about 4 years after Piggybak was released, and we’ve
decided to retire it. Not only did I leave the maintenance up to End Point
after I left to work as an in-house software engineer for the last couple
of years, but I was also in a position to evaluate using Piggybak as
the base for a custom solution.&lt;/p&gt;
&lt;p&gt;While I think there are some great Ruby on Rails gems to help support
your ecommerce application (see below), one of the main things I realized
was that the modularity in Piggybak often doesn’t suit the needs for the
target audience of custom Ruby on Rails ecommerce platforms.&lt;/p&gt;
&lt;p&gt;These days, here’s my oversimplified categorization of those looking
for ecommerce solutions, divided into two audiences:&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Audience #1: Boilerplate Saas with Theme Customization&lt;/b&gt;&lt;br /&gt;
Those sellers where boilerplate ecommerce solutions work, with simple
product and variant modeling. Shopify, Magento, BigCommerce, WooCommerce
can be decent popular options for that audience, but there are so many
other options out there.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Audience #2: Custom Ecommerce in Ruby on Rails&lt;/b&gt;&lt;br /&gt;
Companies going this route have custom enough needs where a small
team can develop and maintain a custom solution, using Ruby on Rails
efficiently that don’t require them to depend on a pre-existing data
model or business rule.&lt;/p&gt;
&lt;p&gt;Not only did Piggybak suffer from a lack of community involvement, but it
also didn’t suit the needs of the two audiences listed above. Because
it was complex enough written in the form of a Rails gem (engine),
it required a developer with some knowledge to install and customize
further. And because it defined assumptions around the product-variant
data model and business rules for inventory and payment management,
it wasn’t necessarily a good fit for custom ecommerce needs.&lt;/p&gt;
&lt;h3 id=&#34;other-ruby-on-rails-gems&#34;&gt;Other Ruby on Rails Gems&lt;/h3&gt;
&lt;p&gt;While I’m sad to sunset Piggybak, I still believe Rails offers great
options for ecommerce needs, including these popular Ruby on Rails gems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/sferik/rails_admin&#34;&gt;Rails_admin&lt;/a&gt;, active_admin (no longer maintained), &lt;a href=&#34;https://github.com/thoughtbot/administrate&#34;&gt;administrate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/carrierwaveuploader/carrierwave&#34;&gt;Carrierwave&lt;/a&gt;: File attachment management. Has decent third party support.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/plataformatec/devise&#34;&gt;Devise&lt;/a&gt;. User authentication marches on.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/CanCanCommunity/cancancan&#34;&gt;Cancancan&lt;/a&gt;. User authorization.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/kaminari/kaminari&#34;&gt;Kaminari&lt;/a&gt;. Pagination.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://rspec.info/&#34;&gt;RSpec&lt;/a&gt;, &lt;a href=&#34;https://github.com/thoughtbot/factory_bot&#34;&gt;FactoryBot&lt;/a&gt;, &lt;a href=&#34;https://github.com/teamcapybara/capybara&#34;&gt;Capybara&lt;/a&gt;, the testing stack I am most familiar with.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/Shopify/active_shipping&#34;&gt;ActiveShipping&lt;/a&gt;. Shipping for ecommerce.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/activemerchant/active_merchant&#34;&gt;ActiveMerchant&lt;/a&gt;. Payment gateways for ecommerce.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are wondering if there are any Ruby on Rails ecommerce gems still
out there, you can look at &lt;a href=&#34;https://solidus.io/&#34;&gt;Solidus&lt;/a&gt;,
&lt;a href=&#34;https://spreecommerce.org/&#34;&gt;Spree&lt;/a&gt;, and &lt;a href=&#34;http://www.ror-e.com/&#34;&gt;RoR-E&lt;/a&gt;.
End Point has a long history with Spree, and experience with the other
two platforms, but again, the audience of those businesses choosing
a custom path may not want to be tied to the data models and business
rules adopted by these existing platforms.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>A Collaborative Timezone Utility</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2017/10/a-collaborative-timezone-utility/"/>
      <id>https://www.endpointdev.com/blog/2017/10/a-collaborative-timezone-utility/</id>
      <published>2017-10-30T00:00:00+00:00</published>
      <author>
        <name>Joe Marrero</name>
      </author>
      <content type="html">
        &lt;div style=&#34;float: right; width: 300px; padding: 0 0 1em 1em;&#34;&gt;
  &lt;div style=&#34;padding: 1em; border: 1px solid #ccc; border-radius: 6px;&#34;&gt;
    &lt;h3 style=&#34;margin: 0 0 1rem 0;&#34;&gt;Try It Out Yourself&lt;/h3&gt;
    &lt;p&gt;The code for this project is hosted on &lt;a href=&#34;https://github.com/manvscode/timezoner&#34;&gt;GitHub and can be cloned from here.&lt;/a&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;At &lt;a href=&#34;/&#34;&gt;End Point Corporation&lt;/a&gt;, our team is spread out across 10 time zones. This gives us the advantage of being able to work around the clock on projects. When one co-worker leaves for day, another can take over. Consider this scenario. It&amp;rsquo;s Monday evening and Martin needs to continue installing software on that Linux cluster, but it&amp;rsquo;s already 6pm and his wife is going to murder him if he&amp;rsquo;s not ready to go out for their anniversary dinner. Let&amp;rsquo;s see who can take over&amp;hellip; Ah, yes, Sanjay in Bangalore can continue with the maintenance. Tuesday morning, the client wakes up to be surprised that 16 hours of work was completed in a day. With respect to software development, the same efficiencies can be realized by parallelizing tasks across time-zones. Code reviews and further development can be continued after normal business hours.&lt;/p&gt;
&lt;p&gt;With all the blessings of a distributed engineering team, collaborating with co-workers can be, occasionally, challenging. Some of these challenges stem from complexities of our system of time. Every co-worker may be operating in a timezone that is different than yours. Time-zones have an associated offset relative to &lt;a href=&#34;https://en.wikipedia.org/wiki/Coordinated_Universal_Time&#34;&gt;Coordinated Universal Time (UTC)&lt;/a&gt;. These offsets are usually in whole hour increments but they may be any real-valued number.&lt;/p&gt;
&lt;p&gt;For example, &lt;a href=&#34;https://en.wikipedia.org/wiki/Eastern_Time_Zone&#34;&gt;Eastern Standard Time (EST)&lt;/a&gt; has an offset of -5 (five hours behind UTC) and &lt;a href=&#34;https://en.wikipedia.org/wiki/Indian_Standard_Time&#34;&gt;Indian Standard Time (IST)&lt;/a&gt; has an offset of 5.5 (five and half hours ahead of UTC). Furthermore, these UTC offsets can be completely arbitrary. In 1995, &lt;a href=&#34;https://en.wikipedia.org/wiki/Kiribati&#34;&gt;Kiribati&lt;/a&gt;, an island nation in the Pacific, changed its UTC offset from -10 to +14 so that all of its outlying islands can share the same time. To further complicate things, some regions may not observe &lt;a href=&#34;https://en.wikipedia.org/wiki/Daylight_saving_time&#34;&gt;daylight savings time (DST)&lt;/a&gt; while other regions do. In fact, in the United States, Indiana started observing DST on April 2, 2006. Some states like Arizona and Hawaii do not observe DST. Other countries, like Australia, have a similar situation where it&amp;rsquo;s left to local governments to decide whether DST is observed or not. Moreover, although DST usually accounts for adding or subtracting an hour of time, it isn&amp;rsquo;t always one hour. This has historically changed from time to time.&lt;/p&gt;
&lt;p&gt;Now you may begin to imagine the headaches that arise when you need to coordinate with anything involving multiple time-zones. To make all of this easier, you can use a utility that we wrote to do all the time conversions for you. First, you have to add each co-worker&amp;rsquo;s information to a configuration file stored at ~/.timezoner. This configuration file will describe all of your co-worker&amp;rsquo;s contact information and their associated IANA time-zone. As an example, this is what the configuration file looks like:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#888&#34;&gt;# Timezone            Email                  Name              OfficePhone        MobilePhone&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;America/New_York      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;edward@example.com&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Edward Teach &amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;n/a&amp;#34;&lt;/span&gt;              &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;+1 731 555 1234&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;America/New_York      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;henry@dexample.com&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Henry Morgan&amp;#34;&lt;/span&gt;    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;+1 646 555 5678&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;+1 954 555 5678&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;America/New_York      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;john@example.com&amp;#34;&lt;/span&gt;     &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;John Auger&amp;#34;&lt;/span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;n/a&amp;#34;&lt;/span&gt;              &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;+1 902 555 1234&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;America/Denver        &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;sam@example.com&amp;#34;&lt;/span&gt;      &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Samuel Bellamy&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;+1 347 535 1234&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;+1 994 555 5678&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;America/Los_Angeles   &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;william@example.com&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;William Kidd&amp;#34;&lt;/span&gt;    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;+1 330 555 5678&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;+1 305 555 1234&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;America/Los_Angeles   &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;israel@example.com&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;Israel Hands&amp;#34;&lt;/span&gt;    &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;+1 507 555 1234&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#d20;background-color:#fff0f0&#34;&gt;&amp;#34;+1 208 555 5678&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now when I need to coordinate a meeting, I can run the utility with the -T option to see each team member&amp;rsquo;s local time.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center; margin-bottom: 1em;&#34;&gt;&lt;a href=&#34;/blog/2017/10/a-collaborative-timezone-utility/image-0-big.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; data-original-height=&#34;320&#34; data-original-width=&#34;1600&#34; height=&#34;172&#34; src=&#34;/blog/2017/10/a-collaborative-timezone-utility/image-0.png&#34; width=&#34;846&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;With the -U option, you can display each contact separated in groups based on UTC offset.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center; margin-bottom: 1em;&#34;&gt;&lt;a href=&#34;/blog/2017/10/a-collaborative-timezone-utility/image-1-big.png&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; data-original-height=&#34;833&#34; data-original-width=&#34;1600&#34; height=&#34;333&#34; src=&#34;/blog/2017/10/a-collaborative-timezone-utility/image-1.png&#34; width=&#34;640&#34; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;Let us know what you think and if you found this tool helpful.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>FOSDEM 2017: experience, community and good talks</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2017/02/fosdem-2017-experience-community-and/"/>
      <id>https://www.endpointdev.com/blog/2017/02/fosdem-2017-experience-community-and/</id>
      <published>2017-02-13T00:00:00+00:00</published>
      <author>
        <name>Emanuele “Lele” Calò</name>
      </author>
      <content type="html">
        &lt;p&gt;In case you happen to be short on time: my final overall perspective about FOSDEM 2017 is that it was awesome&amp;hellip; with very few downsides.&lt;/p&gt;
&lt;p&gt;If you want the longer version, keep reading cause there’s a lot to know and do at FOSDEM and never enough time, sadly.&lt;/p&gt;
&lt;p&gt;This year I actually took a different approach than last time and decided to concentrate on one main track per day, instead of (literally) jumping from one to the other. While I think that overall this may be a good approach if most of the topics covered in a track are of your interest, that comes at the cost of missing one of the best aspects of FOSDEM which is “variety” in contents and presenters.&lt;/p&gt;
&lt;h3 id=&#34;day-1-backup--recovery&#34;&gt;Day 1: Backup &amp;amp; Recovery&lt;/h3&gt;
&lt;p&gt;For the first day I chose the Backup &amp;amp; Recovery track which hosted talks revolving around three interesting and useful projects: namely &lt;a href=&#34;http://relax-and-recover.org&#34;&gt;REAR (Relax and Recovery)&lt;/a&gt;, &lt;a href=&#34;http://drlm.org&#34;&gt;DRLM&lt;/a&gt;, a wrapper and backup management tool based on REAR and &lt;a href=&#34;https://www.bareos.org/en/&#34;&gt;Bareos&lt;/a&gt;, which is a backup solution forked from &lt;a href=&#34;http://blog.bacula.org/&#34;&gt;Bacula&lt;/a&gt; in 2010 and steadily proceeding and improving since then. Both REAR and DLRM were explained and showcased by some of the respective projects main contributors and creators. As a long time system administrator, I particularly appreciated the pride in using Bash as the main “development platform” for both projects. As &lt;strong&gt;Johannes Meixner&lt;/strong&gt; correctly mentioned, using bash facilitates introduces these tools into your normal workflow with knowledge that you’ll most likely already have as a System Administrator or DevOps, thus allowing you to easily “mold” these scripts to your specific needs without spending weeks to learn how to interact with them.&lt;/p&gt;
&lt;p&gt;During the Day 1 Backup &amp;amp; Recovery track there were also a few speeches from two Bareos developers (&lt;strong&gt;Jörg Steffens&lt;/strong&gt; and &lt;strong&gt;Stephan Dühr&lt;/strong&gt;) that presented many aspects of their great project, ranging from very introductory topics, to providing a common knowledge ground for the audience, up to more in depth topics like software capabilities extension through Python Plugins, or a handful of best practices and common usage scenarios. I also enjoyed the speech about automated testing in REAR, presented by &lt;strong&gt;Gratien D’haese&lt;/strong&gt;, which showed how to leverage common testing paradigms and ideas to double-check a REAR setup for potential unexpected behaviors after updates or on new installations or simply as a fully automated monitoring tool to do sanity checks on the backup data flow. While this testing project was new, it’s already functional and impressive to see at work.&lt;/p&gt;
&lt;h3 id=&#34;day-2-cloud-microservices&#34;&gt;Day 2: Cloud Microservices&lt;/h3&gt;
&lt;p&gt;On the second day I moved in a more &lt;em&gt;“cloudy”&lt;/em&gt; section of the FOSDEM where most of the conferences revolved around &lt;strong&gt;Kubernetes&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt; and more in general the microservices landscape. &lt;strong&gt;CoreOS&lt;/strong&gt; (the company behind the open source distribution) was a major contributor and I liked their Kubernetes presentation by &lt;strong&gt;Josh Wood&lt;/strong&gt; and &lt;strong&gt;Luca Bruno&lt;/strong&gt; which respectively explained the new Kubernetes Operators feature and how containers work under the hood in Kubernetes.&lt;/p&gt;
&lt;p&gt;Around lunch time there was a “nice storm of lightning talks” which kept most of the audience firmly on their seats, especially since the Microservices track room didn’t have a free seat for the entire day. I especially liked the talk from &lt;strong&gt;Spyros Trigazis&lt;/strong&gt; about how CERN created and is maintaining a big OpenStack Magnum (the container integrated version of OpenStack) cloud installation for their internal use.&lt;/p&gt;
&lt;p&gt;Then it was &lt;strong&gt;Chris Down’s&lt;/strong&gt; turn and, while he’s a developer from Facebook, his talk gave the audience a good perspective on the future of CGROUPs in the Linux kernel and how they are already relatively safe and usable, even if not yet officially marked as production ready. While I already knew and used “sysdig” in past as a troubleshooting and investigation tool, it was nice to see one of the main developers, &lt;strong&gt;Jorge Salamero&lt;/strong&gt;, using it and showing alternative approaches such as investigating timeout issues between Kubernetes Docker containers by just sysdig and its many modules and filters. It was really impressive seeing how easy it is to identify cross-containers issues and data flow.&lt;/p&gt;
&lt;h3 id=&#34;atmosphere&#34;&gt;Atmosphere&lt;/h3&gt;
&lt;p&gt;There were a lot of Open Source communities with “advertising desks” and I had a nice talk with a few interesting developers from the CoreOS team or from FSFE (Free Software Foundation Europe). Grabbing as many computer stickers as possible is also mandatory at FOSDEM, so I took my share and my new Thinkpad is way more colorful now. In fact, on a more trivial note, this year the FOSDEM staff decided to sell on sale all the laptops that were used during the video encoding phase for the streaming videos before the upload. These laptops were all IBM Thinkpad X220 and there were only a handful of them (~30) at a very appealing price. In fact, this article is being written from one of those very laptops now as I was one of the lucky few which managed to grab one before they were all gone within an hour or so. So if you’re short of a laptop and happen to be at FOSDEM next year, keep your eyes open cause I think they’ll do it again!&lt;/p&gt;
&lt;p&gt;So what’s not to like in such a wonderful scenario? While I admit that there was a lot to be seen and listened to, I sadly didn’t see any “ground-shaking” innovation this year at FOSDEM. I did see many quality talks and I want to send a special huge “thank you” to all the speakers for the effort and high quality standards that they keep for their FOSDEM talks—​but I didn’t see anything extraordinarily new from what I can remember.&lt;/p&gt;
&lt;p&gt;Bottom line is that I still have yet to find someone who was ever disappointed at FOSDEM, but the content quality varies from presenter to presenter and from year to year, so be sure to check the presentations you want to attend carefully before hand.&lt;/p&gt;
&lt;p&gt;I think that the most fascinating part of FOSDEM is meeting interesting, smart, and like-minded people that would be difficult to reach otherwise.&lt;/p&gt;
&lt;p&gt;In fact, while a good share of the merit should be attributed to the quality of the content presented, I firmly believe that the community feeling that you get at FOSDEM is hard to beat and easy to miss when skipped even for one year.&lt;/p&gt;
&lt;div class=&#34;separator&#34; style=&#34;clear: both; text-align: center;&#34;&gt;&lt;a href=&#34;/blog/2017/02/fosdem-2017-experience-community-and/image-0.jpeg&#34; imageanchor=&#34;1&#34; style=&#34;margin-left: 1em; margin-right: 1em;&#34;&gt;&lt;img border=&#34;0&#34; height=&#34;480&#34; src=&#34;/blog/2017/02/fosdem-2017-experience-community-and/image-0.jpeg&#34; width=&#34;640&#34;/&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p&gt;I’ll see you all next year at FOSDEM then.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Malaysia Open Source Community Meetup Quarter 4 2015 (MOSCMY Q4 2015)</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2016/12/malaysia-open-source-community-meetup/"/>
      <id>https://www.endpointdev.com/blog/2016/12/malaysia-open-source-community-meetup/</id>
      <published>2016-12-09T00:00:00+00:00</published>
      <author>
        <name>Muhammad Najmi bin Ahmad Zabidi</name>
      </author>
      <content type="html">
        &lt;p&gt;After a year, finally I decided to publish this post to all of you!&lt;/p&gt;
&lt;p&gt;On November 26th 2015 I had a chance to give a talk in a local open source conference here in Malaysia. The organizer requested me to specifically deliver a talk on “remote work”. This meetup was organized by Malaysian Development Corporation (MDEC) with the sponsorship of Microsoft Malaysia. Microsoft recently started to become more “open source friendly” given that they are in the effort of pushing their cloud based product, Azure. The full schedule of the event can be referred &lt;a href=&#34;https://web.archive.org/web/20170805182015/http://lanyrd.com/2015/moscmy2015/&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The conference was divided into two sessions; where the morning session was held in Petronas Club, Tower One of Kuala Lumpur City Centre (KLCC) and the other session was held in Microsoft Malaysia’s office in Tower Two KLCC. Generally the morning session was for non parallel track (including my track) and the afternoon sessions were two parallel sessions slot.&lt;/p&gt;
&lt;h3 id=&#34;morning-session&#34;&gt;Morning Session&lt;/h3&gt;
&lt;p&gt;The morning session started with a talk by Dinesh Nair, as the Director of Developer Experience and Evangelism, Microsoft Malaysia. The second session in the morning was delivered by Mr Izzat M. Salihuddin, from Multimedia Development Corporation (MDeC), Malaysia. He spoke on the behalf of MDeC explaining the effort by MDeC as a government wing to realize the local cloud infrastructure. One of the challenges that being mentioned by Mr Izzat was the readiness of physical infrastructure as well as the broadband connectivity for the public. The final slot in the morning was delivered by me in which I explained much of the way of how End Pointers do their job, open source software that we used, as well as how we accomplish our job remotely. The morning session was adjourned with a lunch break.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2016/12/malaysia-open-source-community-meetup/image-0-big.jpeg&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2016/12/malaysia-open-source-community-meetup/image-0.jpeg&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Just in case if you are wondering, this is me delivering the talk on that day&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&#34;afternoon-session&#34;&gt;Afternoon Session&lt;/h3&gt;
&lt;p&gt;The afternoon session was a parallel track session, where I chose to attend a talk on Ubuntu’s &lt;a href=&#34;http://www.ubuntu.com/cloud/juju&#34;&gt;Juju&lt;/a&gt; service. The talk was delivered by Mr Khairul Aizat Kamarudzaman from Informology. Mr Aizat’s slides for his talk could be read &lt;a href=&#34;http://www.slideshare.net/fenris/informology-introduction-to-juju&#34;&gt;here&lt;/a&gt;. Later, Mr Sanjay shared his Asterisk skills in which the server is hosted on the Azure platform. Mr Sanjay showed to us how make phone call from the computer to the mobile phone. Asterisk is different from Skype because it is using an open protocol (SIP) and with open clients. Mr Sanjay showed a demo on his implementation, in which it looks like the setup is to compete with the typical PABX phone system.&lt;/p&gt;
&lt;p&gt;For the next slot I decided to enter the slot on TCPTW kernel patch which was delivered by Mr Faisal from Nexoprima. As far as I understood, Mr Faisal reintroduced his own patch for the Linux kernel in order to handle TCP TIME_WAIT issue which was happened due to extremely busy HTTP requests. Since connection in TIME_WAIT state hold a local port for 1 minute and in many distro the default ports are up to 30,000, the effort put to search for free port(s) will use intensive CPU and it could was CPU cycle to purge tons of TIME_WAIT connections.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;/blog/2016/12/malaysia-open-source-community-meetup/image-1-big.jpeg&#34; imageanchor=&#34;1&#34;&gt;&lt;img border=&#34;0&#34; src=&#34;/blog/2016/12/malaysia-open-source-community-meetup/image-1.jpeg&#34;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Mr Faisal gave his talks on TCPTW kernel patch&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Mr Faisal’s TCPTW patch for CentOS 7 could be viewed &lt;a href=&#34;https://github.com/efaisal/linuxtcptw/blob/master/centos/linux-3.10.0-229.1.2.el7.eafaisal.patch&#34;&gt;here&lt;/a&gt;. His presentation slides could be viewed &lt;a href=&#34;http://www.scribd.com/doc/291238152/TIME-WAIT-Hack-for-High-Performance-Ephemeral-Connection-in-Linux-TCP-Stack&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before went back home, I decided to enter a talk on “Dockerizing IOT Service” by Mr Syukor. In this talk Mr Syukor gave a bit theoretical background on Docker and how it can be used on Raspberry Pi board. You can view his slides &lt;a href=&#34;http://www.slideshare.net/msyukor/dockerizing-iot-services&#34;&gt;here&lt;/a&gt;. My personal thought is that Raspberry Pi is versatile enough to run any modern operating system and Docker should not be much an issue.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Happy 10th birthday, Git!</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/04/happy-10th-birthday-git/"/>
      <id>https://www.endpointdev.com/blog/2015/04/happy-10th-birthday-git/</id>
      <published>2015-04-08T00:00:00+00:00</published>
      <author>
        <name>Jon Jensen</name>
      </author>
      <content type="html">
        &lt;p&gt;Git’s birthday was yesterday. It is now 10 years old! Happy birthday, Git!&lt;/p&gt;
&lt;p&gt;Git was born on 7 April 2005, as its creator &lt;a href=&#34;http://marc.info/?l=git&amp;amp;m=117254154130732&#34;&gt;Linus Torvalds recounted in a 2007 mailing list post&lt;/a&gt;. At least if we consider the achievement of self-hosting to be “birth” for software like this. :)&lt;/p&gt;
&lt;p&gt;Birthdays are really arbitrary moments in time, but they give us a reason to pause and reflect back. Why is Git a big deal?&lt;/p&gt;
&lt;p&gt;Even if Git were still relatively obscure, for any serious software project to survive a decade and still be useful and maintained is an accomplishment. But Git is not just surviving.&lt;/p&gt;
&lt;p&gt;Over the past 5–6 years, Git has become the standard version control system in the free software / open source world, and more recently, it is becoming the default version control system everywhere, including in the proprietary software world. It is amazing to consider how fast it has overtaken the older systems, and won out against competing newer systems too. It is not unreasonable these days to expect anyone who does software development, and especially anyone who claims to be familiar with version control systems, to be comfortable with Git.&lt;/p&gt;
&lt;p&gt;So how did I get to be friends with Git, and end up at this birthday celebration?&lt;/p&gt;
&lt;p&gt;After experimenting with Git and other distributed version control systems for a while in early and mid-2007, I started using Git for real work in July 2007. That is the earliest commit date in one my personal Git repositories (which was converted from an earlier CVS repository I started in 2000). Within a few weeks I was in love with Git. It was so obviously vastly superior to CVS and Subversion that I had mostly used before. It offered so much more power, control, and flexibility. The learning curve was real but tractable and it was so much easier to prevent or repair mistakes that I didn’t mind the retraining at all.&lt;/p&gt;
&lt;p&gt;So I’m sounding like a fanboy. What was so much better?&lt;/p&gt;
&lt;p&gt;First, the design. A distributed system where all commits were objects with a SHA-1 hash to identify them and the parent commit(s). Locally editable history. Piecemeal committing thanks to the staging power of the Git index. Cheap and quick branching. Better merging. A commit log that was really useful. Implicit rename tracking. Easy tagging and commit naming. And nothing missing from other systems that I needed.&lt;/p&gt;
&lt;p&gt;Next, the implementation. Trivial setup, with no political and system administrative fuss for client or server. No messing with users and permissions and committer identities, just name &amp;amp; email address like we’re all used to. An efficient wire protocol. Simple ssh transport for pushes and/or pulls of remote repositories, if needed. A single .git directory at the repository root, rather than RCS, CVS, or .svn directories scattered throughout the checkout. A simple .git/config configuration file. And speed, so much speed, even for very large repositories with lots of binary blobs.&lt;/p&gt;
&lt;p&gt;The speed is worth talking about more.&lt;/p&gt;
&lt;p&gt;The speed of Git mattered, and was more than just a bonus. It proved true once again the adage that a big enough quantitative difference becomes a qualitative difference. Some people believed that speed of operations wasn’t all that important, but once you are able to complete your version control tasks so quickly that they’re not at all bothersome, it changes the way you work.&lt;/p&gt;
&lt;p&gt;The ease of setting up an in-place repository on a whim, without worrying about where or if a central repository would ever be made, let alone wasting any time with access control, is a huge benefit. I used to administer CVS and Subversion repositories and life is so much better with the diminished role a “Git repository administrator” plays now.&lt;/p&gt;
&lt;p&gt;Cheap topic branches for little experiments are easy. Committing every little thing separately makes sense if I can later reorder or combine or split my commits, and craft a sane commit before pushing it out where anyone else sees it.&lt;/p&gt;
&lt;p&gt;Git subsumed everything else for us.&lt;/p&gt;
&lt;p&gt;RCS, despite its major limitations, stuck around because CVS and Subversion didn’t do the nice quick in-place versioning that RCS does. That kind of workflow is so useful for a system administrator or ad-hoc local development work. But RCS has an ugly implementation and is based on changing single files, not sets. It can’t be promoted to real distributed version control later if needed. At End Point we used to use CVS, Subversion, and SVK (a distributed system built on top of Subversion), and also RCS for those cases where it still proved useful. Git replaced them all.&lt;/p&gt;
&lt;p&gt;Distributed is better. Even for those who mostly use Git working against a central repository.&lt;/p&gt;
&lt;p&gt;The RCS use case was a special limited subset of the bigger topic of distributed version control, which many people resisted and thought was overkill, or out of control, or whatever. But it is essential, and was key to fixing a lot of the problems of CVS and Subversion. Getting over the mental block of Git not having a single sequential integer revision number for each commit as Subversion did was hard for some people, but it forces us to confront the new reality: Commits are their own objects with an independent identity in a distributed world.&lt;/p&gt;
&lt;p&gt;When I started using Git, Mercurial and Bazaar were the strongest distributed version control competitors. They were roughly feature-equivalent, and were solid contenders. But they were never as fast or compact on disk, and didn’t have Git’s index, cheap branching, stashing, or so many other niceties.&lt;/p&gt;
&lt;p&gt;Then there is the ecosystem.&lt;/p&gt;
&lt;p&gt;GitHub arrived on the scene as an unnecessary appendage at first, but its ease of use and popularity, and social coding encouragement, quickly made it an essential part of the Git community. It turned the occasional propagandistic accusation that Git was antisocial and would encourage project forks, into a virtue, by calling everyone’s clone a “fork”.&lt;/p&gt;
&lt;p&gt;Over time GitHub has played a major role in making Git as popular as it is. Bypassing the need to set up any server software at all to get a central Git repository going removed a hurdle for many people. GitHub is a centralized service that can go down, but that is no serious risk in a distributed system where you generally have full repository mirrors all over the place, and can switch to other hosting any time if needed.&lt;/p&gt;
&lt;p&gt;I realize I’m gushing praise embarrassingly at this point. I find it is warranted, based on my nearly 8 years of using Git and with good familiarity with the alternatives, old and new.&lt;/p&gt;
&lt;p&gt;Thanks, Linus, for Git. Thanks, Junio C Hamano, who has maintained the Git open source project since early on.&lt;/p&gt;
&lt;p&gt;Presumably someday something better will come along. Until then let’s enjoy this rare period of calm where there is an obvious winner to a common technology question and thus no needless debate before work can begin. And the current tool stability means we don’t have to learn new version control skills every few years.&lt;/p&gt;
&lt;p&gt;To commemorate this 10-year birthday, &lt;a href=&#34;http://www.linux.com/news/featured-blogs/185-jennifer-cloer/821541-10-years-of-git-an-interview-with-git-creator-linus-torvalds&#34;&gt;Linux.com interviewed Linus&lt;/a&gt; and it is a worthwhile read.&lt;/p&gt;
&lt;p&gt;You may also be interested to read more in the &lt;a href=&#34;https://en.wikipedia.org/wiki/Git_(software)&#34;&gt;Wikipedia article on Git&lt;/a&gt; or the &lt;a href=&#34;https://git.wiki.kernel.org/index.php/GitHistory&#34;&gt;Git wiki’s history of Git&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, an author named Stephen has written an article called &lt;a href=&#34;http://www.netinstructions.com/the-case-for-git/&#34;&gt;The case for Git in 2015&lt;/a&gt;, which revisits the question of which version control system to use as if it were not yet a settled question. It has many good reminders of why Git has earned its prominent position.&lt;/p&gt;

      </content>
    </entry>
  
    <entry>
      <title>Impressions from Open Source work with Elixir</title>
      <link rel="alternate" href="https://www.endpointdev.com/blog/2015/03/impressions-from-open-source-work-with/"/>
      <id>https://www.endpointdev.com/blog/2015/03/impressions-from-open-source-work-with/</id>
      <published>2015-03-26T00:00:00+00:00</published>
      <author>
        <name>Kamil Ciemniewski</name>
      </author>
      <content type="html">
        &lt;p&gt;Some time ago I started working on the Elixir library that would allow me to send emails as easily as ActionMailer known from the Ruby world does.&lt;/p&gt;
&lt;p&gt;The beginnings were exciting—​I got to play with a &lt;strong&gt;very&lt;/strong&gt; clean and elegant new language which Elixir is. I also quickly learned about the openness of the Elixir community. After hacking some first draft-like version and posting it on GitHub and Google groups—​I got a &lt;strong&gt;very&lt;/strong&gt; warm and thorough code review from the &lt;strong&gt;language’s author&lt;/strong&gt; José Valim! That’s just impressive and it made me even more motivated to help out the community by getting my early code into a better shape.&lt;/p&gt;
&lt;p&gt;Coding the ActionMailer like library in a language that was born 3 years ago doesn’t sound like a few hours job—​there’s lots of functionality to be covered. An email’s body has to be somehow compiled from the template but also the email message has to be transformed to the form in which the SMTP server can digest and relay it. It’s also great if the message’s body can be encoded with „quoted printable”—​this makes even the oldest SMTP server happy. But there’s lots more: connecting with external SMTP servers, using the local in-Elixir implementation, ability to test etc…&lt;/p&gt;
&lt;p&gt;Fortunately Elixir’s built on top of the Erlang’s „Virtual Machine”—​BEAM—​which makes you able to use its libraries—​a lot of them. For the huge part of the functionality I needed to cover I chose the great &lt;a href=&#34;https://github.com/Vagabond/gen_smtp&#34;&gt;gen_smtp library&lt;/a&gt;. This allowed me to actually send emails to SMTP servers and have them properly encoded. With the focus on developer’s productivity, Elixir made me come up with the nice set of other features that you can check out &lt;a href=&#34;https://github.com/kamilc/mailman&#34;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This serves as a shout out blog post for the Elixir ecosystem and community. The friendliness that it radiates with makes open source work like this very rewarding. I invite you to make your contributions as well—​you’ll like it.&lt;/p&gt;

      </content>
    </entry>
  
</feed>
