RSS aggregator JS Ecosystem News
Live feed0 visits
10 today 12 unread
Lead story dev.to 40 minutes ago Fresh today Unread

OpenLiDARViewer: A Browser-Based LiDAR and Point-Cloud Viewer

Rendering LiDAR Scans in the Browser Without Uploading Anything
Most point-cloud workflows still start the same way.
Install a desktop tool.
Import the scan.
Wait.
Inspect.
Measure.
Export.
That workflow makes sense when you are doing serious GIS, photogrammetry, survey processing, classification, or production work.
But there is another moment that is much simpler:
You just received a scan and want to know what is inside it.
Does it open?
Is it clean?
Does it have color, intensity, or classification?
Can I measure something quickly?
Can I show it to someone without making them install software?
That is the moment I wanted to improve with OpenLiDARViewer.
It is an open-source, browser-based LiDAR and point-cloud viewer built around one simple idea:
Drop a scan into the browser and inspect it locally.
No upload.
No account.
No desktop install.
No conversion step for supported formats.
Live demo: https://lidar.aurtech.mx/
GitHub: https://github.com/Aurtechmx/openlidarviewer/
The real goal: own the first 60 seconds
There are already great tools for point-cloud work.
CloudCompare is powerful.
QGIS is powerful.
Potree is excellent for publishing point clouds on the web.
Professional LiDAR software exists for a reason.
OpenLiDARViewer is not trying to replace those tools.
It is focused on a smaller, earlier step:
the first 60 seconds after you get a scan.
Before you process it.
Before you classify it.
Before you prepare a report.
Before you decide what workflow it belongs to.
Sometimes you just need a fast first look.
That sounds simple, but LiDAR data makes “just open it” surprisingly complicated.
Keeping the scan on the device
One thing I wanted from the start was simple:
The scan should not have to leave the user’s machine just to be inspected.
For LiDAR and 3D scan data, that matters.
A scan can represent a private site, a client project, a building, a terrain model, an industrial asset, or a research dataset. Uploading it to a random cloud viewer just to take a quick look is not always acceptable.
So OpenLiDARViewer runs client-side.
The browser loads the app, but the scan itself is read, parsed, analyzed, and rendered locally.
There is no backend parser.
No server-side preprocessing.
No cloud ingestion pipeline.
No “upload your file and wait.”
Just the browser, the GPU, and the file on your machine.
Supporting real-world scan formats
The viewer now supports common point-cloud and scan formats such as:
LAS
LAZ
E57
PLY
OBJ
GLB / GLTF
XYZ
CSV

That mix is intentional.
I wanted the same viewer to handle both:
drone / aerial LiDAR
phone and mobile 3D scans

Those worlds often feel separate, but in practice they are starting to overlap. People capture data from drones, phones, scanners, photogrammetry tools, and AR apps. A lightweight viewer should not care where the scan came from as long as the data can be parsed and rendered.
E57 support was especially important because it is a serious professional exchange format. It also brings more complexity: metadata, scan structure, transforms, optional attributes, and vendor differences.
Getting that kind of file working in the browser is not just a checkbox. It is an interoperability milestone.
Measurement had to become more than a demo feature
The first measurement tool was simple: pick two points and get a distance.
Useful, but limited.
The newer measurement workflow is more practical. It includes:
distance
polyline
area
height
angle
slope
unit switching
editable points
clearer measurement sessions

It does make it more useful for quick inspection.
Scan intelligence: not just seeing the cloud
A point cloud viewer should do more than show dots.
When I open a scan, I want quick answers:
How many points are in this file?
What is the extent?
What is the approximate density?
Does it have RGB?
Does it have intensity?
Does it have classification?
Are there invalid coordinates?
Are there suspicious outliers?
Does the decoded data match what the file claims?

That is why OpenLiDARViewer includes scan intelligence and validation modules.
The goal is not to replace professional QA/QC.
The goal is to give a useful first read before committing to deeper processing.
Seeing the scan matters.
Understanding whether the scan looks intact matters too.
Why the interface is more game-like than GIS-like
A lot of spatial tools inherit GIS interaction patterns.
That is fine for GIS users, but it can be intimidating for people who just want to move through a 3D scan.
OpenLiDARViewer uses a more direct navigation model:
Orbit mode
Walk mode
Fly mode
WASD movement
mouse-look
touch support on mobile
point inspection
saved viewpoints

The goal is to make the scan feel like a space you can enter, not just a dataset you loaded.
Orbit works well for objects and small sites.
Walk makes more sense for interiors or street-level scans.
Fly feels better for terrain, drone LiDAR, and wide-area data.
This is one of the parts I care about most because good interaction design can make technical data much easier to understand.
Mobile was not optional
At first, this kind of tool feels desktop-first.
Then reality shows up.
People want to open scans:
on site
on tablets
on phones
during a demo
while moving between field and office workflows

Mobile support creates its own set of problems:
touch controls
viewport behavior
limited memory
smaller screens
browser gestures
GPU differences
file picker behavior

A viewer that technically opens on mobile but feels terrible is not really mobile-friendly.
So mobile support became part of the direction, especially for phone scan exports and quick review workflows.
Feedback I am looking for
If you work with any of these areas, I would really value your feedback:
LiDAR
UAV mapping
GIS
photogrammetry
3D scanning
WebGL / WebGPU
point-cloud visualization
browser-based technical tools

The most useful feedback would be:
Does your file open?
Does performance feel acceptable?
Are the controls intuitive?
Are the measurement tools useful?
Does E57 behave correctly with your files?
What format support is missing?
What breaks?
What would make this useful in a real workflow?

Live demo: https://lidar.aurtech.mx/
GitHub: https://github.com/Aurtechmx/openlidarviewer/
If the project is useful, a GitHub star helps. But real feedback from people who work with point-cloud data helps even more.
Final thought
The browser is becoming a serious runtime for technical software.
Not for everything.
Not for every workflow.
But for the first step — opening, inspecting, measuring, and understanding spatial data — it is starting to make a lot of sense.
That is the direction OpenLiDARViewer is exploring.
May 22, 2026, 11:08 PMSignal 3
Open article
dev.to3 hours ago
Unread Today

Notifications start small. "Send the user an email when their order ships." A function. A library. Done. A...

Open
dev.to4 hours ago
Unread Today

"One recurring issue in enterprise Angular apps: forms that start simple… then become entire application pl...

Open
dev.to7 hours ago
Unread Today

Security monitoring tools store your real session tokens. Every JWT. Every credential. For every user, acro...

Open

Live feed

Latest ranked updates

1 / 9
github.blog 8 hours ago Today Unread
We are committed to empowering every developer by building an open, secure, and AI-powered platform that defines the future of software development.
The post GitHub recognized as a Leader in the Gartner® Magic Quadrant™ for Enterprise AI Coding Agents for the third year in a row appeared first on The GitHub Blog.
Open 4 pts · May 22, 2026, 4:10 PM
medium.com 8 hours ago Today Unread

Advanced TypeScript Patterns Every Senior Developer Uses in 2026
Continue reading on Medium »
Open 3 pts · May 22, 2026, 3:31 PM
smashingmagazine.com 11 hours ago Today Unread
What people say, feel, think, and do are often very different things. To understand the underlying reasons for user behavior, it helps to look beyond the surface and explore hidden motivations, root causes, and the different layers of reality that shape how people act. Brought to you by Measuring UX Impact, **friendly video course on UX** and design patterns by Vitaly.
Open 2 pts · May 22, 2026, 1:00 PM
dev.to 11 hours ago Today Unread
The problem
During development, bundle size creep goes unnoticed — there is no fast way to see that a code change just added 50 KB to your dist folder until it surfaces in a PR review or a production performance regression.
If you've hit this before, you know how it goes — you end up finding out during a PR review, or worse, after a production deploy.
As a solution, I created buildwatch
Watch your build output directory and report file size changes on every rebuild
It's zero-dependency Node.js, so you can run it immediately without installing anything:

npx buildwatch ./dist
Output:

buildwatch v1.0.0 Watching /projects/myapp/dist Initial state: 3 files, 1.0 MB total [10:23:41] 2 files changed main.js 136.4 KB +12.1 KB (+9.7%) main.css 8.3 KB +0.1 KB (+1.2%) vendor.js 892.1 KB no change Total: 1,036.8 KB +12.2 KB (+1.2%)
How it works
Pure Node.js using fs.watch() for directory monitoring and fs.stat() for file sizes, with 300ms debouncing to handle rapid build events, printing ANSI-colored per-file and total size deltas.
Why I built it
Found repeated complaints in r/webdev and r/node about not noticing bundle size regressions until PR review or production. Existing tools like bundlesize and size-limit require CI config changes or build plugin integration. No popular zero-dep tool gives you instant terminal feedback during local development — a file watcher that just runs alongside your dev server fills this gap cleanly.
Try it
npx buildwatch --help
Part of µ micro — one new developer tool, shipped every day. All tools are zero-dependency Node.js and run instantly with npx.
Open 3 pts · May 22, 2026, 12:45 PM
dev.to 12 hours ago Today Unread
Every developer building with Large Language Models eventually hits the same painful reality: the API bill always catches up to you. Between massive system instructions, multi-turn chat histories, and heavy Retrieval-Augmented Generation (RAG) contexts, prompt sizes explode fast. And since LLM providers charge you per token for every single request, you are constantly paying a premium for linguistic filler words (the, is, and, available) that the AI models don't even need to understand your intent.
I wanted a way to automatically strip out prompt waste and cut my API costs without rewriting my entire application logic.
So, I built and shipped llm-cost-optimizer-node—a zero-config, drop-in client wrapper that intercepts outgoing messages, optimizes them in the cloud, and pipes them seamlessly to your LLM provider.
The Architecture: How it Works Under the Hood
The entire philosophy of this tool is zero structural friction. Instead of forcing you to manually pass every string through an optimization utility before a fetch request, it acts as a local proxy wrapper around your initialized client instance.
Intercept: The wrapper captures the outgoing payload right as chat.completions.create is fired.

Optimize: It securely runs the text blocks through an engine to handle minification, stop-word stripping, or stemming.

Log & Pipe: It prints the exact token savings straight to your development terminal and forwards the lean prompt to the LLM.

Show Me the Code
Integrating it takes exactly three lines of code. You wrap your native client instance once, and leave the rest of your codebase completely untouched.

const { OpenAI } = require('openai'); const { wrapClient } = require('llm-cost-optimizer-node'); // 1. Initialize and wrap your standard client instance const openai = wrapClient(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), { rapidApiKey: process.env.RAPID_API_KEY, strategy: ["minify", "strip_stopwords"] }); // 2. Run your existing production code exactly as before! const response = await openai.chat.completions.create({ model: "gpt-4o", messages: [ { role: "system", content: "You are a warehouse assistant." }, { role: "user", content: "The ergonomic office chair is highly accessible and available in warehouse-4 right now." } ] });
🟢 The Terminal Output
The moment that request executes, your console streams live telemetry showing you exactly how much money and context window you just saved:

--- [Optimizer Proxy] Intercepting Outgoing Messages... --- 🟢 [Metrics] Msg 0 | Slashed: 35 -> 28 tokens (20.00% Saved)
Engineering for Production: Fail-Safe Execution
When building developer infrastructure, application uptime is non-negotiable. I didn't want a network hiccup or an expired API key to crash a production system.
To solve this, the SDK is built with a strict fail-safe guardrail loop:

try { const compressed = await callOptimizationEngine(text); return compressed; } catch (error) { console.warn(`⚠️ [Optimizer Proxy Warning] Compression failed: ${error.message}`); return originalText; // Transparent fallback fallback execution }
If your network goes down or the gateway API hits a rate limit, the client wrapper instantly catches the exception, prints a subtle warning to your server logs, and safely drops back to forwarding your original untouched prompt to your LLM provider. Your application production uptime remains completely bulletproof.
Try It Out!
The package is fully open-source and live on the global npm registry right now.
NPM: npm install llm-cost-optimizer-node
GitHub: https://github.com/Buddy-Henderson/llm-cost-optimizer-node

I'm currently working on adding specialized optimization profiles for heavy RAG workflows and complex Agent state loops.
I'd love to hear your thoughts! What optimization strategies are you using to keep your production LLM bills under control? Drop a comment below!
Open 5 pts · May 22, 2026, 11:27 AM
medium.com 13 hours ago Today Unread

From v1.0 to v1.6 — what I rebuilt, what I borrowed, and what’s running in production for $0/month
Continue reading on Medium »
Open 3 pts · May 22, 2026, 10:20 AM
dev.to yesterday Unread
Over the last few weeks, I've been working on a front-end application project built with Vue.js and ApexCharts that displays time-series data about different sensors installed in different rooms and buildings.
ApexCharts is an excellent library for creating interactive charts, and integrating it in Vue.js is really a piece of cake. However, when it comes to displaying a time-series chart with thousands of points, the performance can suffer, sometimes causing the page to freeze during the rendering or when the user zooms or navigates through the data.
Downsampling data
To solve this issue, it is recommended to downsample the data to reduce the number of displayed points, especially when the selected time range is large enough that highly granular data is unnecessary. If the user later needs to inspect detailed data, the application can switch back to raw data after zooming in.
The criteria for switching between downsampled and raw data depends on the use case. It can be based on the zoom level, the selected date range, or the number of points to display.
The problem
Switching between raw and downsampled data also requires filtering the points to display according to the selected time range. And this is where the problem appears: once you filter data, the zooming and scrolling events of the chart stop working properly when using the mouse scroll. In the era of AI and agent-based coding, solving the issue might seem trivial. But after several attempts and different interactions, I could not find a complete working example that fully
solved the problem.
The solution
Here, I would like to propose a solution that includes the use of downsampled data when the number of displayed points is high enough to avoid performance issues with a line chart and switches to raw data otherwise. The solution is based on the zoomed, scrolled and beforeResetZoom chart events while still relying on the reactive state of the Vue.js component.
To make the solution work correctly, it is also necessary to include a hidden dataset containing the minimum and maximum dates of the complete time series, both with null values. This hidden dataset allows the chart to zoom out completely and pan across the entire x-axis using both the chart controls and the mouse wheel.
Without this additional dataset, these interactions only work correctly when using the built-in chart buttons.
How to do it?
The setup
In a real application, the data usually comes from an API or another external source. After loading the data, each dataset can be downsampled and stored in the component’s reactive state.
Alternatively, downsampling could also be performed in the backend before sending the data to the frontend, depending on how much control you have over the application architecture.
For this example, I generate three random temperature datasets with 10,000 points each and downsample them using the Largest-Triangle Three-Buckets (LTTB) algorithm provided by the downsample package, reducing each dataset to 700 points.

import {LTTB} from 'downsample/methods/LTTB'; let northStationData = generateTemperaturePoints({ startDate: new Date('2026-01-01T00:00:00'), count: 10000, intervalMs: 60 * 1000, baseTemp: 15, dailyAmplitude: 6.5, noise: 1.1, warmingTrendPerHour: 0.0021 }); let northStationLTTB = LTTB(northStationData, 700); this.data = { northStation: { raw: northStationData, downsample: northStationLTTB } }
We also need to store the minimum and maximum dates across all datasets, since they will later be used to create the hidden dataset.

const allX = [northStationData, cityCenterData, southStationData].flatMap(s => s.map(p => p.x)); let minimumDate = Math.min(...allX); let maximumDate = Math.max(...allX); this.ranges = { minimumDate: minimumDate, maximumDate: maximumDate, };
Computed properties
Dataset with minimum and maximum dates
Once the minimum and maximum dates are stored in the reactive state, we can create a computed property that returns a dataset containing only those boundary dates.

plotMinMaxDataset() { return { name: 'rangeMinMaxInvisible', data: [ {x: new Date(this.ranges.minimumDate).getTime(), y: null}, {x: new Date(this.ranges.maximumDate).getTime(), y: null}, ], showInLegend: false, }; }
Initial datasets
When displaying the chart for the first time, we can create another computed property that returns the downsampled datasets together with the hidden boundary dataset.

plotSeries() { let seriesData = []; Object.keys(this.data).forEach(key => { seriesData.push({ name: `${this.data[key].label} (downsampled)`, data: toRaw(this.data[key].downsample), }) }); /** * Add the minimum and maximum points available in the datasets * to avoid zooming and scrolling to lose the minimum and maximum ranges. */ seriesData.push(toRaw(this.plotMinMaxDataset)); return seriesData; }
Methods
Switching between raw and downsampled data
This method is called whenever the user zooms or scrolls through the chart. It filters the data according to the selected range and switches between raw and downsampled datasets based on a defined criterion. At the end, it also includes the dataset with the minimum and maximum dates to avoid zooming and scrolling to lose the original boundaries.
In this example, the criterion is the number of points available in the filtered raw dataset.

plotRangeOnChange(chartContext, minDate, maxDate) { let dataUpdate = []; Object.keys(this.data).forEach(key => { let dataTypeName = 'raw' let dataRaw = pointFilter(this.data[key].raw, minDate, maxDate); /** * Based on the number of points in the raw dataset after filtering, * decide whether to keep raw or downsampled datasets. * * The criteria in this case is to verify the number of points, but it could * also be the range of dates. */ if (dataRaw.length > 1000) { dataRaw = pointFilter(this.data[key].downsample, minDate, maxDate); dataTypeName = 'downsampled'; } dataUpdate.push({ name: `${this.data[key].label} (${dataTypeName})`, data: toRaw(dataRaw), }); }); /** * Add the minimum and maximum points available in the datasets * to avoid zooming and scrolling to lose the minimum and maximum ranges. */ dataUpdate.push(toRaw(this.plotMinMaxDataset)); chartContext.updateOptions({ series: toRaw(dataUpdate), xaxis: { min: minDate, max: maxDate, } }); }
Configuring the chart
We need to configure the zoomed and scrolled events to trigger the switch between raw and downsampled data using the method defined above. We also configure the beforeResetZoom event to restore the chart to the full available date range.
These events provide:
chartContext, which gives access to the chart instance.
xaxis, which contains the currently visible minimum and maximum dates.

At this stage, we also hide the auxiliary hidden dataset from the legend and remove its marker by filtering the series name in the legend.formatter of the plot options and setting its marker size to 0.

let markersSize = []; Object.keys(this.data).forEach(key => { markersSize.push(8); }); markersSize.push(0); let plotOptions = { chart: { id: 'vuechart-example', events: { /** * Update data based on the selected range in the x-axis when events occur */ zoomed: (chartContext, {xaxis}) => this.plotRangeOnChange(chartContext, xaxis.min, xaxis.max), scrolled: (chartContext, {xaxis}) => this.plotRangeOnChange(chartContext, xaxis.min, xaxis.max), beforeResetZoom: (chartContext) => this.plotRangeOnChange(chartContext, null, null), }, }, legend: { formatter: function (seriesName) { /** * Hide the marker containing the minimum and maximum values in the x-axis range */ return seriesName === 'rangeMinMaxInvisible' ? null : seriesName; }, markers: { size: markersSize }, }, /* ... */ }
Wrapping up
In this article, I showed how to use downsampling data in a Vue.js + ApexCharts application to reduce the number of points to display and improve the user experience, while still allowing to zoom in, zoom out and scroll over the chart without encountering problems with the mouse wheel.
The complete code of this example is available in the GitHub repository vue-apexcharts-downsample-zoom.
You can also try the live demo here.
Leave a comment if you have any questions or suggestions to improve, I'll be happy to interact with you!
Let's make the world a better place together, one snippet at a time!
Open 3 pts · May 21, 2026, 5:14 PM

Scroll down to load more stories.