DEV Community

vjmap
vjmap

Posted on

How to Display CAD DWG Files in a Web Browser — No Plugins(CAD+WEBGIS)

`

`
If you've ever tried to share a DWG file with someone who doesn't have AutoCAD, you know the pain: they can't open it, exporting to PDF kills the layers, and running a full desktop license just to view drawings is overkill.

What if non-technical users could just open a URL?

This post walks through how to render AutoCAD DWG files directly in a web browser — no plugins, no desktop software, no PDF export — using WebGL and a JavaScript SDK called VJMAP.

Here's what the end result looks like: a fully interactive CAD drawing in the browser, with infinite zoom, pan, layer query, and entity hover highlighting.


Why Is This Harder Than It Sounds?

DWG is Autodesk's proprietary binary format. It contains geometric entities (lines, arcs, polylines, ellipses, splines, blocks, text), layer metadata, coordinate systems, and rendering styles — none of which browsers can parse natively.

The common workarounds all have real tradeoffs:

Export to SVG / PDF — Simple, but lossy. Loses layers, metadata, and is completely static. No interaction possible.

Convert DWG → DXF → GeoJSON → Leaflet/OpenLayers — Doable with GDAL + GeoServer/MapServer, but CAD and GIS have fundamentally different data models. CAD entities like ellipses, splines, and blocks don't map cleanly to GIS point/line/polygon types. You end up with a drawing that looks slightly wrong everywhere: different line styles, missing fonts, distorted geometry.

ActiveX controls — Legacy Windows-only approach, requires browser plugins, and Chrome dropped ActiveX support years ago.

Autodesk Forge / APS — Powerful, but your DWG data must live on Autodesk's cloud. Not an option if you have data sovereignty requirements.

What we actually need is something that:

  1. Parses DWG server-side with full fidelity — no data model conversion
  2. Serves the result as tile data so the browser only loads what's visible
  3. Renders with WebGL for smooth infinite zoom
  4. Exposes a JavaScript API for interaction, querying, and overlays

That's the approach VJMAP takes.


How It Works: The WebGIS Approach

The key insight is to treat CAD drawings the way web maps treat geographic data: tile pyramids.

Traditional approaches download the entire drawing to the browser and render it all at once. For large DWG files (tens of MB or more), this kills both load time and rendering performance.

The WebGIS approach uses space to buy time: the server pre-processes the DWG and generates either raster tiles (PNG images) or vector tiles (Mapbox Vector Tile format). The browser only requests the tiles for the current viewport and zoom level — never the full dataset.

┌─────────────┐     parse &      ┌─────────────────────┐    tile     ┌──────────────┐
│  DWG File   │ ──────────────▶  │  VJMAP Map Service  │ ─────────▶  │   Browser    │
│  (on server)│   render tiles   │  (tile server)      │  on demand  │  (WebGL)     │
└─────────────┘                  └─────────────────────┘             └──────────────┘
                                                                            │
                                                                   vjmap JavaScript SDK
                                                                   layers / query / draw
Enter fullscreen mode Exit fullscreen mode

Raster tiles — PNG images generated server-side, very stable, works on any device. Zero client-side rendering cost.

Vector tiles — Geometric data served in PBF format (Mapbox Vector Tile standard), rendered by WebGL in the browser. Supports dynamic style changes, higher visual quality, and client-side querying of entity properties.

Both modes are supported. Vector tiles give better results when you need to query or highlight individual entities; raster tiles are simpler and more broadly compatible.


Getting Started

Option 1: Plain HTML (no build tools)

The quickest way to try VJMAP is a single HTML file. Here's a complete working example:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>VJMAP DWG Viewer</title>
    <link rel="stylesheet" href="https://vjmap.com/demo/js/vjmap/vjmap.min.css">
    <script src="https://vjmap.com/demo/js/vjmap/vjmap.min.js"></script>
</head>
<body style="margin:0; overflow:hidden; background:#022B4F;">
    <div id="map" style="position:absolute; left:0; right:0; top:0; bottom:0;"></div>
</body>
<script>
(async () => {
    const env = {
        serviceUrl: "https://vjmap.com/server/api/v1",
        accessToken: "your-access-token",
        exampleMapId: "sys_zp"   // use a map ID you've uploaded
    };

    // Create service object
    let svc = new vjmap.Service(env.serviceUrl, env.accessToken);

    // Open a DWG map (first time: pass fileid; after that the server caches it)
    let res = await svc.openMap({
        mapid: env.exampleMapId,
        mapopenway: vjmap.MapOpenWay.GeomRender, // geometric rendering mode
        style: vjmap.openMapDarkStyle()           // dark background style
    });

    if (res.error) {
        console.error(res.error);
        return;
    }

    // Build the coordinate system from the drawing's native bounds
    let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
    let prj = new vjmap.GeoProjection(mapExtent);

    // Create the map — familiar API if you've used Mapbox GL JS
    let map = new vjmap.Map({
        container: 'map',
        style: svc.vectorStyle(),                           // vector tile rendering
        center: prj.toLngLat(mapExtent.center()),
        zoom: 1,
        pitch: 0,
        renderWorldCopies: false
    });

    // Bind the service and projection to the map
    map.attach(svc, prj);
    await map.onLoad();

    // Add a mouse position control (shows CAD coordinates)
    let mousePositionControl = new vjmap.MousePositionControl({ showLatLng: true });
    map.addControl(mousePositionControl, "bottom-left");
})();
</script>
</html>
Enter fullscreen mode Exit fullscreen mode

That's it — open the HTML file in a browser and your DWG is rendered with WebGL.

💡 Note: On the first openMap call for a new mapid, the server parses the DWG and generates the tile cache. This may take a few seconds depending on file size. All subsequent opens are instant.


Option 2: React

Install the SDK:

npm install vjmap
Enter fullscreen mode Exit fullscreen mode

Here's a complete React component using hooks:

// Map.js
import React, { useRef, useEffect } from 'react';
import ReactDOM from 'react-dom';
import vjmap from '!vjmap';              // webpack raw import
import 'vjmap/dist/vjmap.min.css';
import './Map.css';

const Tooltip = ({ prop }) => (
    <div id={`tooltip-${prop.id}`}>
        <strong>Entity ID:</strong> {prop.id}<br />
        <strong>Layer:</strong> {prop.layerName}<br />
        <strong>Type:</strong> {prop.typeName}<br />
        <strong>Color:</strong> <span style={{ color: prop.color }}>{prop.color}</span>
    </div>
);

const Map = () => {
    const mapContainerRef = useRef(null);
    const tooltipRef = useRef(new vjmap.Popup({ offset: 15 }));

    useEffect(() => {
        let map;

        (async () => {
            const env = {
                serviceUrl: "https://vjmap.com/server/api/v1",
                accessToken: "your-access-token",
                exampleMapId: "sys_zp"
            };

            let svc = new vjmap.Service(env.serviceUrl, env.accessToken);

            let res = await svc.openMap({
                mapid: env.exampleMapId,
                mapopenway: vjmap.MapOpenWay.GeomRender,
                style: vjmap.openMapDarkStyle()
            });

            if (res.error) {
                console.error(res.error);
                return;
            }

            let mapExtent = vjmap.GeoBounds.fromString(res.bounds);
            let prj = new vjmap.GeoProjection(mapExtent);

            map = new vjmap.Map({
                container: mapContainerRef.current,
                style: svc.vectorStyle(),
                center: prj.toLngLat(mapExtent.center()),
                zoom: 1,
                pitch: 0,
                renderWorldCopies: false
            });

            map.attach(svc, prj);
            await map.onLoad();

            // Get layer info and entity type names from the server
            const layers = svc.getMapLayers();
            const { entTypeIdMap } = await svc.getConstData();

            // Mouse position control
            map.addControl(new vjmap.MousePositionControl(), "bottom-left");

            // Hover highlighting with tooltip in vector tile mode
            map.enableVectorLayerHoverHighlight((eventName, feature, layer, e) => {
                if (eventName === "mouseleave") {
                    tooltipRef.current.remove();
                    return;
                }

                const prop = feature.properties;
                if (!prop) return;

                prop.layerName = layers[prop.layer].name;
                prop.typeName = entTypeIdMap[prop.type];
                prop.id = feature.id;

                const tooltipNode = document.createElement('div');
                ReactDOM.render(<Tooltip prop={prop} />, tooltipNode);

                tooltipRef.current
                    .setLngLat(e.lngLat)
                    .setDOMContent(tooltipNode)
                    .addTo(map);
            });
        })();

        return () => map?.remove();
    }, []);

    return <div className="map-container" ref={mapContainerRef} />;
};

export default Map;
Enter fullscreen mode Exit fullscreen mode
/* Map.css */
.map-container {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background: #022B4F;
}
Enter fullscreen mode Exit fullscreen mode
// App.js
import React from 'react';
import Map from './Map';

function App() {
    return <div><Map /></div>;
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Option 3: Vue 3

<!-- Map.vue -->
<script setup lang="ts">
import 'vjmap/dist/vjmap.min.css'
import { Map } from "vjmap"
import { onMounted, provide, ref } from 'vue';
import { enableHoverHighlight, initMap } from './lib/map';

let map: Map;
const isLoaded = ref(false);

onMounted(async () => {
    map = await initMap();
    isLoaded.value = true;
    await enableHoverHighlight(map);
});

provide('map', () => map); // inject map into child components
</script>

<template>
    <div>
        <div id="map-container" />
        <UILayer v-if="isLoaded" />
    </div>
</template>

<style scoped>
#map-container {
    position: absolute;
    top: 0; bottom: 0;
    left: 0; right: 0;
    background: #022B4F;
}
</style>
Enter fullscreen mode Exit fullscreen mode
<!-- App.vue -->
<script setup lang="ts">
import Map from './Map.vue'
</script>

<template>
    <Map />
</template>
Enter fullscreen mode Exit fullscreen mode

Working with Layers and Entities

Query layers from the DWG

// Returns all layers defined in the DWG file
const layers = svc.getMapLayers();
console.log(layers.map(l => l.name));
// e.g. ['0', 'WALLS', 'DOORS', 'ELECTRICAL', 'DIMENSIONS', 'TEXT']
Enter fullscreen mode Exit fullscreen mode

Highlight on hover — vector tile mode

// In vector tile mode, use enableVectorLayerHoverHighlight
map.enableVectorLayerHoverHighlight((eventName, feature, layer, e) => {
    const prop = feature.properties;
    console.log(`Layer: ${layers[prop.layer].name}, Type: ${entTypeIdMap[prop.type]}`);
});
Enter fullscreen mode Exit fullscreen mode

Highlight on click — raster tile mode

// In raster tile mode, use enableLayerClickHighlight
map.enableLayerClickHighlight(svc, e => {
    console.log(`type: ${e.name}, id: ${e.objectid}, layer: ${e.layerindex}`);
});
Enter fullscreen mode Exit fullscreen mode

Raster vs Vector Tiles: Which Should You Use?

Raster Tiles Vector Tiles
How it works Server renders PNG images Server sends PBF geometry; browser renders with WebGL
Visual quality Good Excellent (resolution-independent)
Tile size Larger Smaller
Dynamic styling
Client entity querying
Browser requirements Low HTML5 / WebGL
Best for Simple viewing Interactive apps

To switch between them, just change the style:

// Vector tiles (recommended for interactive apps)
style: svc.vectorStyle()

// Raster tiles (simpler, broader device compatibility)
style: svc.rasterStyle()
Enter fullscreen mode Exit fullscreen mode

Adding Overlays

VJMAP supports adding any overlay on top of the CAD drawing — markers, polygons, popups, extruded 3D shapes.

Add a marker with a popup

// Convert a CAD coordinate point to map lat/lng
const cadPoint = new vjmap.Point(587500025.09578, 3103899983.60220);
const pt = map.toLngLat(cadPoint);

const popup = new vjmap.Popup({ closeOnClick: false, offset: 5 })
    .setHTML('<p>This is a location in the drawing</p>');

const marker = new vjmap.Marker();
marker.setLngLat(pt).setPopup(popup).addTo(map);
marker.togglePopup();
Enter fullscreen mode Exit fullscreen mode

Add 3D extruded shapes

const mapBounds = map.getGeoBounds(0.4);
const geoDatas = [];

for (let i = 0; i < 100; i++) {
    const len = vjmap.randInt(mapBounds.width() / 200, mapBounds.width() / 100);
    const p1 = mapBounds.randomPoint();
    const pts = [
        p1,
        vjmap.geoPoint([p1.x, p1.y + len]),
        vjmap.geoPoint([p1.x + len, p1.y + len]),
        vjmap.geoPoint([p1.x + len, p1.y])
    ];
    geoDatas.push({
        points: map.toLngLat(pts),
        properties: {
            name: `square${i + 1}`,
            color: vjmap.randomColor(),
            height: prj.toMeter(vjmap.randInt(len * 10, len * 20)),
            baseHeight: 0
        }
    });
}

const fillExtrusions = new vjmap.FillExtrusion({
    data: geoDatas,
    fillExtrusionColor: ['case',
        ['to-boolean', ['feature-state', 'hover']], 'red',
        ['get', 'color']
    ],
    fillExtrusionOpacity: 0.8,
    fillExtrusionHeight: ['get', 'height'],
    fillExtrusionBase: ['get', 'baseHeight'],
    isHoverPointer: true,
    isHoverFeatureState: true
});

fillExtrusions.addTo(map);
fillExtrusions.clickLayer(e =>
    console.log(`Clicked: ${e.features[0].properties.name}`)
);
fillExtrusions.hoverPopup(
    f => `<h3>${f.properties.name}</h3>Color: ${f.properties.color}`,
    { anchor: 'bottom' }
);
Enter fullscreen mode Exit fullscreen mode

Drawing Tools

VJMAP includes built-in drawing tools you can add to the map:

const draw = new vjmap.Draw.Tool();
map.addControl(draw, 'top-right');

// Pre-populate with a polygon
const mapBounds = map.getGeoBounds(0.4);
draw.set(vjmap.createPolygonGeoJson(map.toLngLat(mapBounds.randomPoints(3, 3))));

// Start drawing mode
draw.changeMode("draw_polygon");
Enter fullscreen mode Exit fullscreen mode

Deployment

VJMAP supports private deployment — your DWG files never have to leave your own infrastructure.

Trial / development: Connect directly to the VJMAP cloud service at https://vjmap.com/server/api/v1. The free trial has watermarks, upload size limits, and uploaded data may be periodically cleared. Feature set is identical to the licensed version.

Production: Purchase a license and deploy on your own server. VJMAP provides installation packages for:

  • Windows — installer-based setup
  • Linux — binary package (supports AMD64 and ARM64, compatible with mainstream domestic CPUs and OS)
  • Docker — restore from a licensed image provided after purchase

🔒 For Docker deployment and private server setup, contact vjmapcom@163.com after purchasing a license.

Private deployment gives you full control over your data, no watermarks, no file size limits, and the ability to serve large enterprise drawings.


2D and 3D — Same Codebase

One of VJMAP's more useful features is that 2D and 3D views are handled by the same SDK. The pitch parameter on the map object controls the view angle:

// 2D view
let map = new vjmap.Map({ ..., pitch: 0 });

// 3D tilted view
let map = new vjmap.Map({ ..., pitch: 60 });
Enter fullscreen mode Exit fullscreen mode

VJMAP3D extends this with a full Three.js-based 3D engine for more complex spatial visualization, using the same map IDs and service API.


Summary

Here's what we covered:

  • Why native DWG rendering in browsers is hard, and why the "convert to GeoJSON" approach has fundamental limitations
  • How the WebGIS tile pyramid approach solves the performance and fidelity problems
  • The difference between raster and vector tile modes and when to use each
  • Working code examples for plain HTML, React, and Vue 3
  • How to query layers, highlight entities, and add overlays
  • Drawing tool integration
  • Deployment options from trial to production private deployment

The result: a fully interactive CAD drawing viewer that runs in any modern browser, with no AutoCAD license, no plugins, and no export step required.


Resources:


Have you worked with CAD data on the web before? Curious what approach you've taken — drop a comment below.

Top comments (0)