Display 2nd category, only once, as sub-heading, in the loop

You really should try to avoid using query_posts if at all possible.

See When to Use WP Query vs Query Posts vs Get Posts

Here is a pass through a loop using WP_Query:

$args = array(
    'tax_query' => array(
        array(
            'taxonomy' => 'region',
            'field' => 'id',
            'terms' => $region_id
        )
    )
);
$properties = new WP_Query( $args );
if ( ! $properties->have_posts() ) {
    # This should not happen, but just in case
    echo "<p>No properties in this region!</p>";
} else {
    while( $properties->have_posts() ) {
        $property = $properties->next_post();
        $property_id = $property->ID;
        $property_url = get_permalink( $property->ID );
        $property_title = get_the_title( $property->ID );
    }
    # If you need to do the loop over later
    # $properties->rewind_posts();
}

Also, I’d recommend using get_terms to grab your regions:

$regions = get_terms( 'region' );
if ( is_wp_error( $regions ) ) {
    die( $regions );
}

That being said, you have a few different options on how to do this. I’d recommend mapping out the flow of how you are going to do it then write your code.

  • Grab all regions having properties in them

  • Loop through each region drawing your header and grabbing all properties in the region

  • Loop through each property in the region

While doing this loop, you can use get_the_terms( $property-ID, 'property_type' ) to gather all of the property types for each property.

You might then create an array of property types and create some kind of relation between the property types and the properties.

For example:

# Before the loop:
$property_types = array();

# .. looping through the properties ..
if ( $properties->have_posts() ) {
   while ( $properties->have_posts() ) {
      $property = $properties->next_post();
      # Grab all property type terms for this property
      $types = get_the_terms( $property->ID, 'property_type' );
      # Cycle through and build out our property_types array started previously
      foreach ( $types as $type ) {
         # we add them by slug so we can use a key sort later
         if ( !array_key_exists( $type->slug, $property_types ) ) {
            # The slug is not a key in our array, add it and attach the type under a data key
            $property_types["{$type->slug}"]['data'] = $type;
         }
         # Add the property under the properties key for the current type
         # - this will cause properties to possibly show up multiple times
         # - if that is not wanted, do a key check before adding the property
         $property_types["{$type->slug}"]['properties']["{$property->ID}"] = $property;
      }
      # ... anything else in the loop
   }
}

# Sort the property types in this region
ksort( $property_types );

# Cycle through the property types array we built and 
# draw them all out
foreach ( $property_types as $info ) {
   $type_data = $info['data'];
   $properties = $info['properties'];
   $type_url = get_permalink($type_data->term_id);
   $type_name = $type_data->name;
   echo '<h2><a href="' . $type_url . '">' . $type_name . '</a></h2>';
   foreach ( $properties as $property ) {
      $property_url = get_permalink( $property->ID );
      $property_name = get_the_title( $property->ID );
      echo '<p><a href="' . $property_url . '">' . $property_name . '</a></p>';
   }
}

There are many ways to do this, but the above should work reasonably decently.