Search code examples
pythonascii

Parsing ASCII floor plan image in python?


I am trying to identify the number of rooms and furniture(S,C,W,P) in an ASCII floorplan. A typical floorplan looks like this with different rooms and layouts. What would be the best way to tackle this?

+---------------+-------------------+           +----------+
|               |                   |           |          |
|  (office)     |            C      |           |   C      |
|               |                   |           |          |
|           W   |                   +-----------+          |
|               |                   |           |          |
|   S           |   (bathroom)     S|      S    |          |
|           +---+--------+----------+           |          |
|          /P           S|                      |          |
|         /              |                      |          |
|        /   (kitchen)   |      (bedroom)       |  P       |
+-------+                |                      |          |
|        \               |                      |          |
|         \   SSWP       |   W              W   |          |
|          +-------------+----------------------+          |
|                                                          |
|             (hallway)                                    |
|    W                                                     |
+--------------+-------------+-------------+               |
               |             |              \              |
               |             |               \        C    |
               | P           |                \            |
               |             |                 \           |
        +------+           P |                  +----------+
        |S                   |                              
        |    (balcony)   C   |                              
        +--------------------+      

Solution

  • My approach would be:

    • consider the plan as a grid
    • split each row in segments of "space" (non-walls)
    • join spaces that share at least one column
    • look for a name, if found save the joint space as a roon
    • count the furnitures in the room

    That results in the following code (I defined a couple of classes for readability):

    from dataclasses import dataclass, field
    from re import finditer, search
    from typing import Dict, List
    
    @dataclass
    class RowSegment():
        row_no : int
        col_start : int
        col_end : int
    
        def __repr__(self):
            return f'{self.row_no}:{self.col_start}-{self.col_end}'
    
    @dataclass
    class Space():
        name : str = 'noname'
        furn_count : Dict[str, int] = field(default_factory = dict)
        segs : List[RowSegment] = field(default_factory = list)
        complete : bool = False
    
        def __repr__(self):
            return f'{self.name}: {[i for i in self.furn_count.items()]}\n{[s for s in self.segs]}'
    
    plan = """
    +---------------+-------------------+           +----------+
    |               |                   |           |          |
    |  (office)     |            C      |           |   C      |
    |               |                   |           |          |
    |           W   |                   +-----------+          |
    |               |                   |           |          |
    |   S           |   (bathroom)     S|      S    |          |
    |           +---+--------+----------+           |          |
    |          /P           S|                      |          |
    |         /              |                      |          |
    |        /   (kitchen)   |      (bedroom)       |  P       |
    +-------+                |                      |          |
    |        \               |                      |          |
    |         \   SSWP       |   W              W   |          |
    |          +-------------+----------------------+          |
    |                                                          |
    |             (hallway)                                    |
    |    W                                                     |
    +--------------+-------------+-------------+               |
                   |             |              \              |
                   |             |               \        C    |
                   | P           |                \            |
                   |             |                 \           |
            +------+           P |                  +----------+
            |S                   |                              
            |    (balcony)   C   |                              
            +--------------------+                              """
    
    furn_types = 'CPSW'
    workspaces = []
    rooms = []
    rows = plan.split('\n')
    rows.pop(0)
    
    for no, row in enumerate(rows):
        found = list(finditer(r'[^/\\|+-]+', row))
        if found:
            rsegs = [RowSegment(no, rs.start(), rs.end()) for rs in found]
            for ws in workspaces:
                for rs in rsegs:
                    if max(ws.segs[-1].col_start, rs.col_start) < min(ws.segs[-1].col_end, rs.col_end): #add rs to ws
                        ws.segs.append(rs)
                        rsegs.remove(rs)
                        break
                else: #no rs added => complete
                    text = ''.join([rows[s.row_no][s.col_start:s.col_end] for s in ws.segs])
                    name = search(r'\(\w+\)', text)
                    if name:
                        ws.name = name[0][1:-1]
                        ws.furn_count = {f: text.count(f) for f in furn_types}
                        rooms.append(ws)
                    ws.complete = True
            #reset ws list to only not complete
            workspaces = [ws for ws in workspaces if ws.complete == False]
            #create new wss with remaining rss
            for rs in rsegs:
                newws = Space()
                newws.segs.append(rs)
                workspaces.append(newws)
    for r in rooms:
        print(r)
    

    The output (I'm also printing the "spaces" that compose each room) is:

    bathroom: [('C', 1), ('P', 0), ('S', 1), ('W', 0)]
    [1:17-36, 2:17-36, 3:17-36, 4:17-36, 5:17-36, 6:17-36]
    office: [('C', 0), ('P', 0), ('S', 1), ('W', 1)]
    [1:1-16, 2:1-16, 3:1-16, 4:1-16, 5:1-16, 6:1-16, 7:1-12, 8:1-11, 9:1-10, 10:1-9]
    bedroom: [('C', 0), ('P', 0), ('S', 1), ('W', 2)]
    [5:37-48, 6:37-48, 7:37-48, 8:26-48, 9:26-48, 10:26-48, 11:26-48, 12:26-48, 13:26-48]
    kitchen: [('C', 0), ('P', 2), ('S', 3), ('W', 1)]
    [8:12-25, 9:11-25, 10:10-25, 11:9-25, 12:10-25, 13:11-25]
    hallway: [('C', 2), ('P', 1), ('S', 0), ('W', 1)]
    [1:49-59, 2:49-59, 3:49-59, 4:49-59, 5:49-59, 6:49-59, 7:49-59, 8:49-59, 9:49-59, 10:49-59, 11:49-59, 12:49-59, 13:49-59, 14:49-59, 15:1-59, 16:1-59, 17:1-59, 18:44-59, 19:45-59, 20:46-59, 21:47-59, 22:48-59]
    balcony: [('C', 1), ('P', 2), ('S', 1), ('W', 0)]
    [19:16-29, 20:16-29, 21:16-29, 22:16-29, 23:16-29, 24:9-29, 25:9-29]